use std::error::Error as StdError;
use std::fmt::{self, Display, Formatter, Write};
use std::str::FromStr;
use enum_dispatch::enum_dispatch;
use serde::{Deserialize, Serialize};
use serde_with::base64::Base64;
use serde_with::{DeserializeAs, DisplayFromStr, IfIsHumanReadable, SerializeAs, serde_as};
use sui_sdk_types::{
Address, ConsensusDeterminedVersionAssignments, Digest, EpochId, GasCostSummary, MoveLocation,
TypeTag, UserSignature, Version,
};
use tabled::builder::Builder as TableBuilder;
use tabled::settings::style::HorizontalLine;
use tabled::settings::{Panel as TablePanel, Style as TableStyle};
use super::balance_changes::BalanceChange;
use super::object_changes::ObjectChange;
use super::{Page, SuiEvent, SuiObjectRef};
use crate::msgs::Owner;
use crate::serde::BigInt;
pub type SuiEpochId = BigInt<u64>;
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(
rename_all = "camelCase",
rename = "TransactionBlockResponseQuery",
default
)]
pub struct SuiTransactionBlockResponseQuery {
pub filter: Option<TransactionFilter>,
pub options: Option<SuiTransactionBlockResponseOptions>,
}
impl SuiTransactionBlockResponseQuery {
pub fn new(
filter: Option<TransactionFilter>,
options: Option<SuiTransactionBlockResponseOptions>,
) -> Self {
Self { filter, options }
}
pub fn new_with_filter(filter: TransactionFilter) -> Self {
Self {
filter: Some(filter),
options: None,
}
}
}
pub type TransactionBlocksPage = Page<SuiTransactionBlockResponse, Digest>;
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq, Default)]
#[serde(
rename_all = "camelCase",
rename = "TransactionBlockResponseOptions",
default
)]
pub struct SuiTransactionBlockResponseOptions {
pub show_input: bool,
pub show_raw_input: bool,
pub show_effects: bool,
pub show_events: bool,
pub show_object_changes: bool,
pub show_balance_changes: bool,
pub show_raw_effects: bool,
}
impl SuiTransactionBlockResponseOptions {
pub fn new() -> Self {
Self::default()
}
pub fn full_content() -> Self {
Self {
show_effects: true,
show_input: true,
show_raw_input: true,
show_events: true,
show_object_changes: true,
show_balance_changes: true,
show_raw_effects: false,
}
}
pub fn with_input(mut self) -> Self {
self.show_input = true;
self
}
pub fn with_raw_input(mut self) -> Self {
self.show_raw_input = true;
self
}
pub fn with_effects(mut self) -> Self {
self.show_effects = true;
self
}
pub fn with_events(mut self) -> Self {
self.show_events = true;
self
}
pub fn with_balance_changes(mut self) -> Self {
self.show_balance_changes = true;
self
}
pub fn with_object_changes(mut self) -> Self {
self.show_object_changes = true;
self
}
pub fn with_raw_effects(mut self) -> Self {
self.show_raw_effects = true;
self
}
pub fn default_execution_request_type(&self) -> ExecuteTransactionRequestType {
if self.require_effects() {
ExecuteTransactionRequestType::WaitForLocalExecution
} else {
ExecuteTransactionRequestType::WaitForEffectsCert
}
}
pub fn require_input(&self) -> bool {
self.show_input || self.show_raw_input || self.show_object_changes
}
pub fn require_effects(&self) -> bool {
self.show_effects
|| self.show_events
|| self.show_balance_changes
|| self.show_object_changes
|| self.show_raw_effects
}
pub fn only_digest(&self) -> bool {
self == &Self::default()
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum ExecuteTransactionRequestType {
WaitForEffectsCert,
WaitForLocalExecution,
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase", rename = "TransactionBlockResponse")]
pub struct SuiTransactionBlockResponse {
pub digest: Digest,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction: Option<SuiTransactionBlock>,
#[serde_as(as = "Base64")]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub raw_transaction: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub effects: Option<SuiTransactionBlockEffects>,
#[serde(skip_serializing_if = "Option::is_none")]
pub events: Option<SuiTransactionBlockEvents>,
#[serde(skip_serializing_if = "Option::is_none")]
pub object_changes: Option<Vec<ObjectChange>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub balance_changes: Option<Vec<BalanceChange>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<BigInt<u64>>")]
pub timestamp_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confirmed_local_execution: Option<bool>,
#[serde_as(as = "Option<BigInt<u64>>")]
#[serde(skip_serializing_if = "Option::is_none")]
pub checkpoint: Option<Version>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub errors: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub raw_effects: Vec<u8>,
}
impl SuiTransactionBlockResponse {
pub fn new(digest: Digest) -> Self {
Self {
digest,
..Default::default()
}
}
pub fn status_ok(&self) -> Option<bool> {
self.effects.as_ref().map(|e| e.status().is_ok())
}
pub fn sui_effects(&self) -> Result<Option<sui_sdk_types::TransactionEffects>, ToEffectsError> {
self.raw_effects
.len()
.ge(&0)
.then_some(sui_sdk_types::bcs::FromBcs::from_bcs(&self.raw_effects))
.transpose()
.map_err(From::from)
.map_err(ToEffectsError::Generic)
}
pub fn get_transaction(
&self,
) -> Result<&SuiTransactionBlock, SuiTransactionBlockResponseError> {
self.transaction
.as_ref()
.ok_or(SuiTransactionBlockResponseError::MissingTransaction)
}
pub fn get_effects(
&self,
) -> Result<&SuiTransactionBlockEffectsV1, SuiTransactionBlockResponseError> {
let SuiTransactionBlockEffects::V1(effects) = self
.effects
.as_ref()
.ok_or(SuiTransactionBlockResponseError::MissingEffects)?;
Ok(effects)
}
pub fn get_events(
&self,
) -> Result<&SuiTransactionBlockEvents, SuiTransactionBlockResponseError> {
self.events
.as_ref()
.ok_or(SuiTransactionBlockResponseError::MissingEvents)
}
pub fn get_object_changes(
&self,
) -> Result<&Vec<ObjectChange>, SuiTransactionBlockResponseError> {
self.object_changes
.as_ref()
.ok_or(SuiTransactionBlockResponseError::MissingObjectChanges)
}
pub fn get_balance_changes(
&self,
) -> Result<&Vec<BalanceChange>, SuiTransactionBlockResponseError> {
self.balance_changes
.as_ref()
.ok_or(SuiTransactionBlockResponseError::MissingBalanceChanges)
}
pub fn try_check_execution_status(&self) -> Result<(), SuiTransactionBlockResponseError> {
if let Some(SuiTransactionBlockEffects::V1(effects)) = &self.effects
&& let SuiExecutionStatus::Failure { error } = &effects.status
{
return Err(SuiTransactionBlockResponseError::ExecutionFailure(
error.clone(),
));
}
Ok(())
}
pub fn check_execution_status(&self) -> Result<(), SuiTransactionBlockResponseError> {
let Some(SuiTransactionBlockEffects::V1(effects)) = &self.effects else {
return Err(SuiTransactionBlockResponseError::MissingEffects);
};
if let SuiExecutionStatus::Failure { error } = &effects.status {
return Err(SuiTransactionBlockResponseError::ExecutionFailure(
error.clone(),
));
}
Ok(())
}
pub fn published_package_id(&self) -> Result<Address, SuiTransactionBlockResponseError> {
for change in self.get_object_changes()? {
if let ObjectChange::Published { package_id, .. } = change {
return Ok(*package_id);
}
}
Err(SuiTransactionBlockResponseError::NoPublishedPackage)
}
pub fn into_object_changes(
self,
) -> Result<Vec<ObjectChange>, SuiTransactionBlockResponseError> {
let Self { object_changes, .. } = self;
object_changes.ok_or(SuiTransactionBlockResponseError::MissingObjectChanges)
}
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum ToEffectsError {
#[error("Generic: {0:#}")]
Generic(Box<dyn StdError + Send + Sync + 'static>),
}
#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq)]
pub enum SuiTransactionBlockResponseError {
#[error("No transaction in response")]
MissingTransaction,
#[error("No effects in response")]
MissingEffects,
#[error("No events in response")]
MissingEvents,
#[error("No object changes in response")]
MissingObjectChanges,
#[error("No balance changes in response")]
MissingBalanceChanges,
#[error("Failed to execute transaction block: {0}")]
ExecutionFailure(String),
#[error("No 'Published' object change")]
NoPublishedPackage,
}
impl PartialEq for SuiTransactionBlockResponse {
fn eq(&self, other: &Self) -> bool {
self.transaction == other.transaction
&& self.effects == other.effects
&& self.timestamp_ms == other.timestamp_ms
&& self.confirmed_local_execution == other.confirmed_local_execution
&& self.checkpoint == other.checkpoint
}
}
impl Display for SuiTransactionBlockResponse {
fn fmt(&self, writer: &mut Formatter<'_>) -> fmt::Result {
writeln!(writer, "Transaction Digest: {}", &self.digest)?;
if let Some(t) = &self.transaction {
writeln!(writer, "{}", t)?;
}
if let Some(e) = &self.effects {
writeln!(writer, "{}", e)?;
}
if let Some(e) = &self.events {
writeln!(writer, "{}", e)?;
}
if let Some(object_changes) = &self.object_changes {
let mut builder = TableBuilder::default();
let (
mut created,
mut deleted,
mut mutated,
mut published,
mut transferred,
mut wrapped,
) = (vec![], vec![], vec![], vec![], vec![], vec![]);
for obj in object_changes {
match obj {
ObjectChange::Created { .. } => created.push(obj),
ObjectChange::Deleted { .. } => deleted.push(obj),
ObjectChange::Mutated { .. } => mutated.push(obj),
ObjectChange::Published { .. } => published.push(obj),
ObjectChange::Transferred { .. } => transferred.push(obj),
ObjectChange::Wrapped { .. } => wrapped.push(obj),
};
}
write_obj_changes(created, "Created", &mut builder)?;
write_obj_changes(deleted, "Deleted", &mut builder)?;
write_obj_changes(mutated, "Mutated", &mut builder)?;
write_obj_changes(published, "Published", &mut builder)?;
write_obj_changes(transferred, "Transferred", &mut builder)?;
write_obj_changes(wrapped, "Wrapped", &mut builder)?;
let mut table = builder.build();
table.with(TablePanel::header("Object Changes"));
table.with(
TableStyle::rounded()
.horizontals([(1, HorizontalLine::inherit(TableStyle::modern()))]),
);
writeln!(writer, "{}", table)?;
}
if let Some(balance_changes) = &self.balance_changes {
let mut builder = TableBuilder::default();
for balance in balance_changes {
builder.push_record(vec![format!("{}", balance)]);
}
let mut table = builder.build();
table.with(TablePanel::header("Balance Changes"));
table.with(
TableStyle::rounded()
.horizontals([(1, HorizontalLine::inherit(TableStyle::modern()))]),
);
writeln!(writer, "{}", table)?;
}
Ok(())
}
}
fn write_obj_changes<T: Display>(
values: Vec<T>,
output_string: &str,
builder: &mut TableBuilder,
) -> std::fmt::Result {
if !values.is_empty() {
builder.push_record(vec![format!("{} Objects: ", output_string)]);
for obj in values {
builder.push_record(vec![format!("{}", obj)]);
}
}
Ok(())
}
pub fn get_new_package_obj_from_response(
response: &SuiTransactionBlockResponse,
) -> Option<(Address, Version, Digest)> {
response.object_changes.as_ref().and_then(|changes| {
changes
.iter()
.find(|change| matches!(change, ObjectChange::Published { .. }))
.map(|change| change.object_ref())
})
}
pub fn get_new_package_upgrade_cap_from_response(
response: &SuiTransactionBlockResponse,
) -> Option<(Address, Version, Digest)> {
response.object_changes.as_ref().and_then(|changes| {
changes
.iter()
.find(|change| {
matches!(change, ObjectChange::Created {
owner: Owner::AddressOwner(_),
object_type,
..
} if *object_type.address() == Address::TWO && object_type.module().as_str() == "package" && object_type.name().as_str() == "UpgradeCap")
})
.map(|change| change.object_ref())
})
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename = "TransactionBlockKind", tag = "kind")]
#[non_exhaustive]
pub enum SuiTransactionBlockKind {
ChangeEpoch(SuiChangeEpoch),
Genesis(SuiGenesisTransaction),
ConsensusCommitPrologue(SuiConsensusCommitPrologue),
ProgrammableTransaction(SuiProgrammableTransactionBlock),
AuthenticatorStateUpdate(SuiAuthenticatorStateUpdate),
RandomnessStateUpdate(SuiRandomnessStateUpdate),
EndOfEpochTransaction(SuiEndOfEpochTransaction),
ConsensusCommitPrologueV2(SuiConsensusCommitPrologueV2),
ConsensusCommitPrologueV3(SuiConsensusCommitPrologueV3),
}
impl Display for SuiTransactionBlockKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut writer = String::new();
match &self {
Self::ChangeEpoch(e) => {
writeln!(writer, "Transaction Kind: Epoch Change")?;
writeln!(writer, "New epoch ID: {}", e.epoch)?;
writeln!(writer, "Storage gas reward: {}", e.storage_charge)?;
writeln!(writer, "Computation gas reward: {}", e.computation_charge)?;
writeln!(writer, "Storage rebate: {}", e.storage_rebate)?;
writeln!(writer, "Timestamp: {}", e.epoch_start_timestamp_ms)?;
}
Self::Genesis(_) => {
writeln!(writer, "Transaction Kind: Genesis Transaction")?;
}
Self::ConsensusCommitPrologue(p) => {
writeln!(writer, "Transaction Kind: Consensus Commit Prologue")?;
writeln!(
writer,
"Epoch: {}, Round: {}, Timestamp: {}",
p.epoch, p.round, p.commit_timestamp_ms
)?;
}
Self::ConsensusCommitPrologueV2(p) => {
writeln!(writer, "Transaction Kind: Consensus Commit Prologue V2")?;
writeln!(
writer,
"Epoch: {}, Round: {}, Timestamp: {}, Digest: {}",
p.epoch, p.round, p.commit_timestamp_ms, p.consensus_commit_digest
)?;
}
Self::ConsensusCommitPrologueV3(p) => {
writeln!(writer, "Transaction Kind: Consensus Commit Prologue V3")?;
writeln!(
writer,
"Epoch: {}, Round: {}, SubDagIndex: {:?}, Timestamp: {}, Digest: {}",
p.epoch,
p.round,
p.sub_dag_index,
p.commit_timestamp_ms,
p.consensus_commit_digest
)?;
}
Self::ProgrammableTransaction(p) => {
write!(writer, "Transaction Kind: Programmable")?;
write!(writer, "{}", super::displays::Pretty(p))?;
}
Self::AuthenticatorStateUpdate(_) => {
writeln!(writer, "Transaction Kind: Authenticator State Update")?;
}
Self::RandomnessStateUpdate(_) => {
writeln!(writer, "Transaction Kind: Randomness State Update")?;
}
Self::EndOfEpochTransaction(_) => {
writeln!(writer, "Transaction Kind: End of Epoch Transaction")?;
}
}
write!(f, "{}", writer)
}
}
impl SuiTransactionBlockKind {
pub fn transaction_count(&self) -> usize {
match self {
Self::ProgrammableTransaction(p) => p.commands.len(),
_ => 1,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::ChangeEpoch(_) => "ChangeEpoch",
Self::Genesis(_) => "Genesis",
Self::ConsensusCommitPrologue(_) => "ConsensusCommitPrologue",
Self::ConsensusCommitPrologueV2(_) => "ConsensusCommitPrologueV2",
Self::ConsensusCommitPrologueV3(_) => "ConsensusCommitPrologueV3",
Self::ProgrammableTransaction(_) => "ProgrammableTransaction",
Self::AuthenticatorStateUpdate(_) => "AuthenticatorStateUpdate",
Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
Self::EndOfEpochTransaction(_) => "EndOfEpochTransaction",
}
}
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiChangeEpoch {
#[serde_as(as = "BigInt<u64>")]
pub epoch: EpochId,
#[serde_as(as = "BigInt<u64>")]
pub storage_charge: u64,
#[serde_as(as = "BigInt<u64>")]
pub computation_charge: u64,
#[serde_as(as = "BigInt<u64>")]
pub storage_rebate: u64,
#[serde_as(as = "BigInt<u64>")]
pub epoch_start_timestamp_ms: u64,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[enum_dispatch(SuiTransactionBlockEffectsAPI)]
#[serde(
rename = "TransactionBlockEffects",
rename_all = "camelCase",
tag = "messageVersion"
)]
pub enum SuiTransactionBlockEffects {
V1(SuiTransactionBlockEffectsV1),
}
#[enum_dispatch]
pub trait SuiTransactionBlockEffectsAPI {
fn status(&self) -> &SuiExecutionStatus;
fn into_status(self) -> SuiExecutionStatus;
fn shared_objects(&self) -> &[SuiObjectRef];
fn created(&self) -> &[OwnedObjectRef];
fn mutated(&self) -> &[OwnedObjectRef];
fn unwrapped(&self) -> &[OwnedObjectRef];
fn deleted(&self) -> &[SuiObjectRef];
fn unwrapped_then_deleted(&self) -> &[SuiObjectRef];
fn wrapped(&self) -> &[SuiObjectRef];
fn gas_object(&self) -> &OwnedObjectRef;
fn events_digest(&self) -> Option<&Digest>;
fn dependencies(&self) -> &[Digest];
fn executed_epoch(&self) -> EpochId;
fn transaction_digest(&self) -> &Digest;
fn gas_cost_summary(&self) -> &GasCostSummary;
fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef>;
fn modified_at_versions(&self) -> Vec<(Address, Version)>;
fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)>;
fn all_deleted_objects(&self) -> Vec<(&SuiObjectRef, DeleteKind)>;
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum WriteKind {
Mutate,
Create,
Unwrap,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum DeleteKind {
Normal,
UnwrapThenDelete,
Wrap,
}
#[serde_as]
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
#[serde(
rename = "TransactionBlockEffectsModifiedAtVersions",
rename_all = "camelCase"
)]
pub struct SuiTransactionBlockEffectsModifiedAtVersions {
object_id: Address,
#[serde_as(as = "BigInt<u64>")]
sequence_number: Version,
}
#[serde_as]
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
#[serde(rename = "TransactionBlockEffectsV1", rename_all = "camelCase")]
pub struct SuiTransactionBlockEffectsV1 {
pub status: SuiExecutionStatus,
#[serde_as(as = "BigInt<u64>")]
pub executed_epoch: EpochId,
#[serde_as(as = "serde_with::FromInto<crate::serde::GasCostSummaryJson>")]
pub gas_used: GasCostSummary,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub modified_at_versions: Vec<SuiTransactionBlockEffectsModifiedAtVersions>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub shared_objects: Vec<SuiObjectRef>,
pub transaction_digest: Digest,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub created: Vec<OwnedObjectRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mutated: Vec<OwnedObjectRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub unwrapped: Vec<OwnedObjectRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub deleted: Vec<SuiObjectRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub unwrapped_then_deleted: Vec<SuiObjectRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub wrapped: Vec<SuiObjectRef>,
pub gas_object: OwnedObjectRef,
#[serde(skip_serializing_if = "Option::is_none")]
pub events_digest: Option<Digest>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dependencies: Vec<Digest>,
}
impl SuiTransactionBlockEffectsAPI for SuiTransactionBlockEffectsV1 {
fn status(&self) -> &SuiExecutionStatus {
&self.status
}
fn into_status(self) -> SuiExecutionStatus {
self.status
}
fn shared_objects(&self) -> &[SuiObjectRef] {
&self.shared_objects
}
fn created(&self) -> &[OwnedObjectRef] {
&self.created
}
fn mutated(&self) -> &[OwnedObjectRef] {
&self.mutated
}
fn unwrapped(&self) -> &[OwnedObjectRef] {
&self.unwrapped
}
fn deleted(&self) -> &[SuiObjectRef] {
&self.deleted
}
fn unwrapped_then_deleted(&self) -> &[SuiObjectRef] {
&self.unwrapped_then_deleted
}
fn wrapped(&self) -> &[SuiObjectRef] {
&self.wrapped
}
fn gas_object(&self) -> &OwnedObjectRef {
&self.gas_object
}
fn events_digest(&self) -> Option<&Digest> {
self.events_digest.as_ref()
}
fn dependencies(&self) -> &[Digest] {
&self.dependencies
}
fn executed_epoch(&self) -> EpochId {
self.executed_epoch
}
fn transaction_digest(&self) -> &Digest {
&self.transaction_digest
}
fn gas_cost_summary(&self) -> &GasCostSummary {
&self.gas_used
}
fn mutated_excluding_gas(&self) -> Vec<OwnedObjectRef> {
self.mutated
.iter()
.filter(|o| *o != &self.gas_object)
.cloned()
.collect()
}
fn modified_at_versions(&self) -> Vec<(Address, Version)> {
self.modified_at_versions
.iter()
.map(|v| (v.object_id, v.sequence_number))
.collect::<Vec<_>>()
}
fn all_changed_objects(&self) -> Vec<(&OwnedObjectRef, WriteKind)> {
self.mutated
.iter()
.map(|owner_ref| (owner_ref, WriteKind::Mutate))
.chain(
self.created
.iter()
.map(|owner_ref| (owner_ref, WriteKind::Create)),
)
.chain(
self.unwrapped
.iter()
.map(|owner_ref| (owner_ref, WriteKind::Unwrap)),
)
.collect()
}
fn all_deleted_objects(&self) -> Vec<(&SuiObjectRef, DeleteKind)> {
self.deleted
.iter()
.map(|r| (r, DeleteKind::Normal))
.chain(
self.unwrapped_then_deleted
.iter()
.map(|r| (r, DeleteKind::UnwrapThenDelete)),
)
.chain(self.wrapped.iter().map(|r| (r, DeleteKind::Wrap)))
.collect()
}
}
fn owned_objref_string(obj: &OwnedObjectRef) -> String {
format!(
" ┌──\n │ ID: {} \n │ Owner: {:?} \n │ Version: {} \n │ Digest: {}\n └──",
obj.reference.object_id, obj.owner, obj.reference.version, obj.reference.digest
)
}
fn objref_string(obj: &SuiObjectRef) -> String {
format!(
" ┌──\n │ ID: {} \n │ Version: {} \n │ Digest: {}\n └──",
obj.object_id, obj.version, obj.digest
)
}
impl Display for SuiTransactionBlockEffects {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut builder = TableBuilder::default();
builder.push_record(vec![format!("Digest: {}", self.transaction_digest())]);
builder.push_record(vec![format!("Status: {:?}", self.status())]);
builder.push_record(vec![format!("Executed Epoch: {}", self.executed_epoch())]);
if !self.created().is_empty() {
builder.push_record(vec![format!("\nCreated Objects: ")]);
for oref in self.created() {
builder.push_record(vec![owned_objref_string(oref)]);
}
}
if !self.mutated().is_empty() {
builder.push_record(vec![format!("Mutated Objects: ")]);
for oref in self.mutated() {
builder.push_record(vec![owned_objref_string(oref)]);
}
}
if !self.shared_objects().is_empty() {
builder.push_record(vec![format!("Shared Objects: ")]);
for oref in self.shared_objects() {
builder.push_record(vec![objref_string(oref)]);
}
}
if !self.deleted().is_empty() {
builder.push_record(vec![format!("Deleted Objects: ")]);
for oref in self.deleted() {
builder.push_record(vec![objref_string(oref)]);
}
}
if !self.wrapped().is_empty() {
builder.push_record(vec![format!("Wrapped Objects: ")]);
for oref in self.wrapped() {
builder.push_record(vec![objref_string(oref)]);
}
}
if !self.unwrapped().is_empty() {
builder.push_record(vec![format!("Unwrapped Objects: ")]);
for oref in self.unwrapped() {
builder.push_record(vec![owned_objref_string(oref)]);
}
}
builder.push_record(vec![format!(
"Gas Object: \n{}",
owned_objref_string(self.gas_object())
)]);
let gas_cost_summary = self.gas_cost_summary();
builder.push_record(vec![format!(
"Gas Cost Summary:\n \
Storage Cost: {} MIST\n \
Computation Cost: {} MIST\n \
Storage Rebate: {} MIST\n \
Non-refundable Storage Fee: {} MIST",
gas_cost_summary.storage_cost,
gas_cost_summary.computation_cost,
gas_cost_summary.storage_rebate,
gas_cost_summary.non_refundable_storage_fee,
)]);
let dependencies = self.dependencies();
if !dependencies.is_empty() {
builder.push_record(vec![format!("\nTransaction Dependencies:")]);
for dependency in dependencies {
builder.push_record(vec![format!(" {}", dependency)]);
}
}
let mut table = builder.build();
table.with(TablePanel::header("Transaction Effects"));
table.with(
TableStyle::rounded().horizontals([(1, HorizontalLine::inherit(TableStyle::modern()))]),
);
write!(f, "{}", table)
}
}
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct DryRunTransactionBlockResponse {
pub effects: SuiTransactionBlockEffects,
pub events: SuiTransactionBlockEvents,
pub object_changes: Vec<ObjectChange>,
pub balance_changes: Vec<BalanceChange>,
pub input: SuiTransactionBlockData,
#[serde(default)]
pub execution_error_source: Option<String>,
}
#[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename = "TransactionBlockEvents", transparent)]
pub struct SuiTransactionBlockEvents {
pub data: Vec<SuiEvent>,
}
impl Display for SuiTransactionBlockEvents {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.data.is_empty() {
writeln!(f, "â•─────────────────────────────╮")?;
writeln!(f, "│ No transaction block events │")?;
writeln!(f, "╰─────────────────────────────╯")
} else {
let mut builder = TableBuilder::default();
for event in &self.data {
builder.push_record(vec![format!("{}", event)]);
}
let mut table = builder.build();
table.with(TablePanel::header("Transaction Block Events"));
table.with(
TableStyle::rounded()
.horizontals([(1, HorizontalLine::inherit(TableStyle::modern()))]),
);
write!(f, "{}", table)
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename = "DevInspectArgs", rename_all = "camelCase")]
pub struct DevInspectArgs {
pub gas_sponsor: Option<Address>,
pub gas_budget: Option<BigInt<u64>>,
pub gas_objects: Option<Vec<(Address, Version, Digest)>>,
pub skip_checks: Option<bool>,
pub show_raw_txn_data_and_effects: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename = "DevInspectResults", rename_all = "camelCase")]
pub struct DevInspectResults {
pub effects: SuiTransactionBlockEffects,
pub events: SuiTransactionBlockEvents,
#[serde(skip_serializing_if = "Option::is_none")]
pub results: Option<Vec<SuiExecutionResult>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub raw_txn_data: Vec<u8>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub raw_effects: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename = "SuiExecutionResult", rename_all = "camelCase")]
pub struct SuiExecutionResult {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mutable_reference_outputs: Vec<(/* argument */ SuiArgument, Vec<u8>, SuiTypeTag)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub return_values: Vec<(Vec<u8>, SuiTypeTag)>,
}
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum SuiTransactionBlockBuilderMode {
Commit,
DevInspect,
}
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
#[serde(rename = "ExecutionStatus", rename_all = "camelCase", tag = "status")]
pub enum SuiExecutionStatus {
Success,
Failure { error: String },
}
impl Display for SuiExecutionStatus {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Success => write!(f, "success"),
Self::Failure { error } => write!(f, "failure due to {error}"),
}
}
}
impl SuiExecutionStatus {
const MOVE_ABORT_PATTERN: &str = r#"MoveAbort\(MoveLocation \{ module: ModuleId \{ address: ([[:alnum:]]+), name: Identifier\("([[:word:]]+)"\) \}, function: (\d+), instruction: (\d+), function_name: Some\("([[:word:]]+)"\) \}, (\d+)\)"#;
pub fn is_ok(&self) -> bool {
matches!(self, SuiExecutionStatus::Success)
}
pub fn is_err(&self) -> bool {
matches!(self, SuiExecutionStatus::Failure { .. })
}
pub fn as_move_abort(&self) -> Option<(MoveLocation, u64)> {
let Self::Failure { error } = self else {
return None;
};
let re = regex::Regex::new(Self::MOVE_ABORT_PATTERN).expect("Tested below");
let matches = re.captures(error)?;
let address = "0x".to_owned() + matches.get(1)?.as_str();
Some((
MoveLocation {
package: address.parse().ok()?,
module: matches.get(2)?.as_str().parse().ok()?,
function: matches.get(3)?.as_str().parse().ok()?,
instruction: matches.get(4)?.as_str().parse().ok()?,
function_name: Some(matches.get(5)?.as_str().parse().ok()?),
},
matches.get(6)?.as_str().parse().ok()?,
))
}
}
#[serde_as]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(rename = "GasData", rename_all = "camelCase")]
pub struct SuiGasData {
pub payment: Vec<SuiObjectRef>,
pub owner: Address,
#[serde_as(as = "BigInt<u64>")]
pub price: u64,
#[serde_as(as = "BigInt<u64>")]
pub budget: u64,
}
impl Display for SuiGasData {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "Gas Owner: {}", self.owner)?;
writeln!(f, "Gas Budget: {} MIST", self.budget)?;
writeln!(f, "Gas Price: {} MIST", self.price)?;
writeln!(f, "Gas Payment:")?;
for payment in &self.payment {
write!(f, "{} ", objref_string(payment))?;
}
writeln!(f)
}
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[enum_dispatch(SuiTransactionBlockDataAPI)]
#[serde(
rename = "TransactionBlockData",
rename_all = "camelCase",
tag = "messageVersion"
)]
pub enum SuiTransactionBlockData {
V1(SuiTransactionBlockDataV1),
}
#[enum_dispatch]
pub trait SuiTransactionBlockDataAPI {
fn transaction(&self) -> &SuiTransactionBlockKind;
fn sender(&self) -> &Address;
fn gas_data(&self) -> &SuiGasData;
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(rename = "TransactionBlockDataV1", rename_all = "camelCase")]
pub struct SuiTransactionBlockDataV1 {
pub transaction: SuiTransactionBlockKind,
pub sender: Address,
pub gas_data: SuiGasData,
}
impl SuiTransactionBlockDataAPI for SuiTransactionBlockDataV1 {
fn transaction(&self) -> &SuiTransactionBlockKind {
&self.transaction
}
fn sender(&self) -> &Address {
&self.sender
}
fn gas_data(&self) -> &SuiGasData {
&self.gas_data
}
}
impl SuiTransactionBlockData {
pub fn move_calls(&self) -> Vec<&SuiProgrammableMoveCall> {
match self {
Self::V1(data) => match &data.transaction {
SuiTransactionBlockKind::ProgrammableTransaction(pt) => pt
.commands
.iter()
.filter_map(|command| match command {
SuiCommand::MoveCall(c) => Some(&**c),
_ => None,
})
.collect(),
_ => vec![],
},
}
}
}
impl Display for SuiTransactionBlockData {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::V1(data) => {
writeln!(f, "Sender: {}", data.sender)?;
writeln!(f, "{}", self.gas_data())?;
writeln!(f, "{}", data.transaction)
}
}
}
}
#[serde_as]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(rename = "TransactionBlock", rename_all = "camelCase")]
pub struct SuiTransactionBlock {
pub data: SuiTransactionBlockData,
#[serde_as(as = "Vec<IfIsHumanReadable<Base64UserSignature>>")]
pub tx_signatures: Vec<UserSignature>,
}
struct Base64UserSignature;
impl<'de> DeserializeAs<'de, UserSignature> for Base64UserSignature {
fn deserialize_as<D>(deserializer: D) -> Result<UserSignature, D::Error>
where
D: serde::Deserializer<'de>,
{
let base64 = Box::<str>::deserialize(deserializer)?;
UserSignature::from_base64(&base64).map_err(serde::de::Error::custom)
}
}
impl SerializeAs<UserSignature> for Base64UserSignature {
fn serialize_as<S>(source: &UserSignature, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let base64 = source.to_base64();
base64.serialize(serializer)
}
}
impl Display for SuiTransactionBlock {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut builder = TableBuilder::default();
builder.push_record(vec![format!("{}", self.data)]);
builder.push_record(vec![format!("Signatures:")]);
for tx_sig in &self.tx_signatures {
builder.push_record(vec![format!(" {}\n", tx_sig.to_base64())]);
}
let mut table = builder.build();
table.with(TablePanel::header("Transaction Data"));
table.with(
TableStyle::rounded().horizontals([(1, HorizontalLine::inherit(TableStyle::modern()))]),
);
write!(f, "{}", table)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiGenesisTransaction {
pub objects: Vec<Address>,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiConsensusCommitPrologue {
#[serde_as(as = "BigInt<u64>")]
pub epoch: u64,
#[serde_as(as = "BigInt<u64>")]
pub round: u64,
#[serde_as(as = "BigInt<u64>")]
pub commit_timestamp_ms: u64,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiConsensusCommitPrologueV2 {
#[serde_as(as = "BigInt<u64>")]
pub epoch: u64,
#[serde_as(as = "BigInt<u64>")]
pub round: u64,
#[serde_as(as = "BigInt<u64>")]
pub commit_timestamp_ms: u64,
pub consensus_commit_digest: Digest,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiConsensusCommitPrologueV3 {
#[serde_as(as = "BigInt<u64>")]
pub epoch: u64,
#[serde_as(as = "BigInt<u64>")]
pub round: u64,
#[serde_as(as = "Option<BigInt<u64>>")]
pub sub_dag_index: Option<u64>,
#[serde_as(as = "BigInt<u64>")]
pub commit_timestamp_ms: u64,
pub consensus_commit_digest: Digest,
pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiAuthenticatorStateUpdate {
#[serde_as(as = "BigInt<u64>")]
pub epoch: u64,
#[serde_as(as = "BigInt<u64>")]
pub round: u64,
pub new_active_jwks: Vec<SuiActiveJwk>,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiRandomnessStateUpdate {
#[serde_as(as = "BigInt<u64>")]
pub epoch: u64,
#[serde_as(as = "BigInt<u64>")]
pub randomness_round: u64,
pub random_bytes: Vec<u8>,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiEndOfEpochTransaction {
pub transactions: Vec<SuiEndOfEpochTransactionKind>,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub enum SuiEndOfEpochTransactionKind {
ChangeEpoch(SuiChangeEpoch),
AuthenticatorStateCreate,
AuthenticatorStateExpire(SuiAuthenticatorStateExpire),
RandomnessStateCreate,
CoinDenyListStateCreate,
BridgeStateCreate(Digest),
BridgeCommitteeUpdate(Version),
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiAuthenticatorStateExpire {
#[serde_as(as = "BigInt<u64>")]
pub min_epoch: u64,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiActiveJwk {
pub jwk_id: SuiJwkId,
pub jwk: SuiJWK,
#[serde_as(as = "BigInt<u64>")]
pub epoch: u64,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiJwkId {
pub iss: String,
pub kid: String,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiJWK {
pub kty: String,
pub e: String,
pub n: String,
pub alg: String,
}
#[serde_as]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename = "InputObjectKind")]
pub enum SuiInputObjectKind {
MovePackage(Address),
ImmOrOwnedMoveObject(SuiObjectRef),
SharedMoveObject {
id: Address,
#[serde_as(as = "BigInt<u64>")]
initial_shared_version: Version,
#[serde(default)]
mutable: bool,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiProgrammableTransactionBlock {
pub inputs: Vec<SuiCallArg>,
#[serde(rename = "transactions")]
pub commands: Vec<SuiCommand>,
}
impl Display for SuiProgrammableTransactionBlock {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let Self { inputs, commands } = self;
writeln!(f, "Inputs: {inputs:?}")?;
writeln!(f, "Commands: [")?;
for c in commands {
writeln!(f, " {c},")?;
}
writeln!(f, "]")
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename = "SuiTransaction")]
pub enum SuiCommand {
MoveCall(Box<SuiProgrammableMoveCall>),
TransferObjects(Vec<SuiArgument>, SuiArgument),
SplitCoins(SuiArgument, Vec<SuiArgument>),
MergeCoins(SuiArgument, Vec<SuiArgument>),
Publish(Vec<Address>),
Upgrade(Vec<Address>, Address, SuiArgument),
MakeMoveVec(Option<String>, Vec<SuiArgument>),
}
impl Display for SuiCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::MoveCall(p) => {
write!(f, "MoveCall({p})")
}
Self::MakeMoveVec(ty_opt, elems) => {
write!(f, "MakeMoveVec(")?;
if let Some(ty) = ty_opt {
write!(f, "Some{ty}")?;
} else {
write!(f, "None")?;
}
write!(f, ",[")?;
write_sep(f, elems, ",")?;
write!(f, "])")
}
Self::TransferObjects(objs, addr) => {
write!(f, "TransferObjects([")?;
write_sep(f, objs, ",")?;
write!(f, "],{addr})")
}
Self::SplitCoins(coin, amounts) => {
write!(f, "SplitCoins({coin},")?;
write_sep(f, amounts, ",")?;
write!(f, ")")
}
Self::MergeCoins(target, coins) => {
write!(f, "MergeCoins({target},")?;
write_sep(f, coins, ",")?;
write!(f, ")")
}
Self::Publish(deps) => {
write!(f, "Publish(<modules>,")?;
write_sep(f, deps, ",")?;
write!(f, ")")
}
Self::Upgrade(deps, current_package_id, ticket) => {
write!(f, "Upgrade(<modules>, {ticket},")?;
write_sep(f, deps, ",")?;
write!(f, ", {current_package_id}")?;
write!(f, ")")
}
}
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SuiArgument {
GasCoin,
Input(u16),
Result(u16),
NestedResult(u16, u16),
}
impl Display for SuiArgument {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::GasCoin => write!(f, "GasCoin"),
Self::Input(i) => write!(f, "Input({i})"),
Self::Result(i) => write!(f, "Result({i})"),
Self::NestedResult(i, j) => write!(f, "NestedResult({i},{j})"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SuiProgrammableMoveCall {
pub package: Address,
pub module: String,
pub function: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub type_arguments: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub arguments: Vec<SuiArgument>,
}
fn write_sep<T: Display>(
f: &mut Formatter<'_>,
items: impl IntoIterator<Item = T>,
sep: &str,
) -> std::fmt::Result {
let mut xs = items.into_iter().peekable();
while let Some(x) = xs.next() {
write!(f, "{x}")?;
if xs.peek().is_some() {
write!(f, "{sep}")?;
}
}
Ok(())
}
impl Display for SuiProgrammableMoveCall {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let Self {
package,
module,
function,
type_arguments,
arguments,
} = self;
write!(f, "{package}::{module}::{function}")?;
if !type_arguments.is_empty() {
write!(f, "<")?;
write_sep(f, type_arguments, ",")?;
write!(f, ">")?;
}
write!(f, "(")?;
write_sep(f, arguments, ",")?;
write!(f, ")")
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename = "TypeTag", rename_all = "camelCase")]
pub struct SuiTypeTag(String);
impl SuiTypeTag {
pub fn new(tag: String) -> Self {
Self(tag)
}
}
impl TryInto<TypeTag> for SuiTypeTag {
type Error = <TypeTag as FromStr>::Err;
fn try_into(self) -> Result<TypeTag, Self::Error> {
self.0.parse()
}
}
impl From<TypeTag> for SuiTypeTag {
fn from(tag: TypeTag) -> Self {
Self(format!("{}", tag))
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum RPCTransactionRequestParams {
TransferObjectRequestParams(TransferObjectParams),
MoveCallRequestParams(MoveCallParams),
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransferObjectParams {
pub recipient: Address,
pub object_id: Address,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MoveCallParams {
pub package_object_id: Address,
pub module: String,
pub function: String,
#[serde(default)]
pub type_arguments: Vec<SuiTypeTag>,
pub arguments: Vec<serde_json::Value>,
}
#[serde_as]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionBlockBytes {
pub tx_bytes: String,
pub gas: Vec<SuiObjectRef>,
pub input_objects: Vec<SuiInputObjectKind>,
}
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
#[serde(rename = "OwnedObjectRef")]
pub struct OwnedObjectRef {
pub owner: Owner,
pub reference: SuiObjectRef,
}
impl OwnedObjectRef {
pub fn object_id(&self) -> Address {
self.reference.object_id
}
pub fn version(&self) -> Version {
self.reference.version
}
}
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum SuiCallArg {
Object(SuiObjectArg),
Pure(SuiPureValue),
}
impl SuiCallArg {
pub fn pure(&self) -> Option<&serde_json::Value> {
match self {
SuiCallArg::Pure(v) => Some(&v.value),
_ => None,
}
}
pub fn object(&self) -> Option<&Address> {
match self {
SuiCallArg::Object(SuiObjectArg::SharedObject { object_id, .. })
| SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject { object_id, .. })
| SuiCallArg::Object(SuiObjectArg::Receiving { object_id, .. }) => Some(object_id),
_ => None,
}
}
}
#[serde_as]
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SuiPureValue {
#[serde_as(as = "Option<DisplayFromStr>")]
value_type: Option<TypeTag>,
value: serde_json::Value,
}
impl SuiPureValue {
pub fn value(&self) -> serde_json::Value {
self.value.clone()
}
pub fn value_type(&self) -> Option<TypeTag> {
self.value_type.clone()
}
}
#[serde_as]
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "objectType", rename_all = "camelCase")]
pub enum SuiObjectArg {
#[serde(rename_all = "camelCase")]
ImmOrOwnedObject {
object_id: Address,
#[serde_as(as = "BigInt<u64>")]
version: Version,
digest: Digest,
},
#[serde(rename_all = "camelCase")]
SharedObject {
object_id: Address,
#[serde_as(as = "BigInt<u64>")]
initial_shared_version: Version,
mutable: bool,
},
#[serde(rename_all = "camelCase")]
Receiving {
object_id: Address,
#[serde_as(as = "BigInt<u64>")]
version: Version,
digest: Digest,
},
}
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TransactionFilter {
Checkpoint(#[serde_as(as = "IfIsHumanReadable<BigInt<u64>, _>")] Version),
MoveFunction {
package: Address,
module: Option<String>,
function: Option<String>,
},
InputObject(Address),
ChangedObject(Address),
AffectedObject(Address),
FromAddress(Address),
ToAddress(Address),
FromAndToAddress { from: Address, to: Address },
FromOrToAddress { addr: Address },
TransactionKind(String),
TransactionKindIn(Vec<String>),
}
#[cfg(test)]
mod tests {
use color_eyre::Result;
use itertools::Itertools as _;
use sui_sdk_types::Identifier;
use super::*;
const MOVE_ABORT_ERRORS: [&str; 3] = [
r#"MoveAbort(MoveLocation { module: ModuleId { address: fd6f306bb2f8dce24dd3d4a9bdc51a46e7c932b15007d73ac0cfb38c15de0fea, name: Identifier("market") }, function: 1, instruction: 60, function_name: Some("try_update_funding") }, 1001)"#,
r#"MoveAbort(MoveLocation { module: ModuleId { address: 241537381737a40df6838bc395fb64f04ff604513c18a2ac3308ac810c805fa6, name: Identifier("oracle") }, function: 23, instruction: 42, function_name: Some("update_price_feed_inner") }, 4)"#,
r#"MoveAbort(MoveLocation { module: ModuleId { address: 72a8715095cdc8442b4316f78802d7aefa2e6f0c3c6fac256ce81554034b0d4b, name: Identifier("clearing_house") }, function: 53, instruction: 32, function_name: Some("settled_liquidated_position") }, 2001) in command 3"#,
];
#[test]
fn move_abort_regex_is_valid() -> Result<()> {
regex::Regex::new(SuiExecutionStatus::MOVE_ABORT_PATTERN)?;
Ok(())
}
#[test]
fn move_abort_extracts() -> Result<()> {
let expected = [
(
MoveLocation {
package: "0xfd6f306bb2f8dce24dd3d4a9bdc51a46e7c932b15007d73ac0cfb38c15de0fea"
.parse()?,
module: Identifier::from_static("market"),
function: 1,
instruction: 60,
function_name: Some("try_update_funding".parse()?),
},
1001,
),
(
MoveLocation {
package: "0x241537381737a40df6838bc395fb64f04ff604513c18a2ac3308ac810c805fa6"
.parse()?,
module: Identifier::from_static("oracle"),
function: 23,
instruction: 42,
function_name: Some("update_price_feed_inner".parse()?),
},
4,
),
(
MoveLocation {
package: "0x72a8715095cdc8442b4316f78802d7aefa2e6f0c3c6fac256ce81554034b0d4b"
.parse()?,
module: Identifier::from_static("clearing_house"),
function: 53,
instruction: 32,
function_name: Some("settled_liquidated_position".parse()?),
},
2001,
),
];
let errors = MOVE_ABORT_ERRORS
.into_iter()
.map(|msg| SuiExecutionStatus::Failure { error: msg.into() });
for (error, expect) in errors.into_iter().zip_eq(expected) {
assert_eq!(error.as_move_abort(), Some(expect));
}
Ok(())
}
#[test]
fn transaction_block_transaction_deser() {
let value = serde_json::json!({
"data": {
"messageVersion": "v1",
"transaction": {
"kind": "ProgrammableTransaction",
"inputs": [
{
"type": "pure",
"valueType": "u64",
"value": "5"
},
{
"type": "pure",
"valueType": "u64",
"value": "13"
},
{
"type": "pure",
"valueType": "u64",
"value": "20"
},
{
"type": "object",
"objectType": "immOrOwnedObject",
"objectId": "0xa3926c709e6ed570217aec468c9b81c35c4e7178a18f610f4427f4a075f63680",
"version": "3",
"digest": "7YeqF7kSbFrdzE2zyG4BdTctiHQrjapUkzqT3kqGAg6D"
},
{
"type": "pure",
"valueType": "u256",
"value": "100000000000000000"
},
{
"type": "pure",
"valueType": "u256",
"value": "50000000000000000"
},
{
"type": "pure",
"valueType": "u64",
"value": "3600000"
},
{
"type": "pure",
"valueType": "u64",
"value": "86400000"
},
{
"type": "pure",
"valueType": "u64",
"value": "5000"
},
{
"type": "pure",
"valueType": "u256",
"value": "1000000000000000"
},
{
"type": "pure",
"valueType": "u256",
"value": "100000000000000"
},
{
"type": "pure",
"valueType": "u64",
"value": "1000000"
},
{
"type": "pure",
"valueType": "u64",
"value": "1000"
},
{
"type": "object",
"objectType": "immOrOwnedObject",
"objectId": "0xf8cea4da5f0b9cf6cea67217fb99de1ac7dcc905863c5d2b1f8d4ab4530b726a",
"version": "3",
"digest": "6cACC7x9FFY1SdnzT6aH1z9UPMmvm4NGUz3skXGBn1Yd"
},
{
"type": "object",
"objectType": "sharedObject",
"objectId": "0x981bfebd35bf22ab220853d6756568fd0556d127b856d7d08a12822da0cf1a4b",
"initialSharedVersion": "3",
"mutable": true
},
{
"type": "object",
"objectType": "sharedObject",
"objectId": "0xd9b347da14a2600c9392d6718096d712e2b5497ce87c871854bfa92023099530",
"initialSharedVersion": "509",
"mutable": false
},
{
"type": "object",
"objectType": "sharedObject",
"objectId": "0x7bbfd7177ef8042d2752deb2bfd5016114ad91e8c0abae6f78f35b4fd4db95d1",
"initialSharedVersion": "510",
"mutable": false
},
{
"type": "object",
"objectType": "sharedObject",
"objectId": "0x0000000000000000000000000000000000000000000000000000000000000006",
"initialSharedVersion": "1",
"mutable": false
}
],
"transactions": [
{
"MoveCall": {
"package": "0xb9f430cddd9cbe60f0beea60fce6da590688abbf2948dccff7cdda5a874be9ad",
"module": "interface",
"function": "create_orderbook",
"arguments": [
{
"Input": 13
},
{
"Input": 0
},
{
"Input": 1
},
{
"Input": 2
},
{
"Input": 0
},
{
"Input": 1
},
{
"Input": 2
}
]
}
},
{
"MoveCall": {
"package": "0xb9f430cddd9cbe60f0beea60fce6da590688abbf2948dccff7cdda5a874be9ad",
"module": "interface",
"function": "create_clearing_house",
"type_arguments": [
"0x6f048d2b0929f0f2c16dd7ccf643b59b7b029d180766780ba83ce9f847306715::usdc::USDC"
],
"arguments": [
{
"Input": 13
},
{
"Result": 0
},
{
"Input": 3
},
{
"Input": 17
},
{
"Input": 15
},
{
"Input": 16
},
{
"Input": 4
},
{
"Input": 5
},
{
"Input": 6
},
{
"Input": 7
},
{
"Input": 8
},
{
"Input": 6
},
{
"Input": 8
},
{
"Input": 6
},
{
"Input": 9
},
{
"Input": 9
},
{
"Input": 9
},
{
"Input": 10
},
{
"Input": 9
},
{
"Input": 11
},
{
"Input": 12
}
]
}
},
{
"MoveCall": {
"package": "0xb9f430cddd9cbe60f0beea60fce6da590688abbf2948dccff7cdda5a874be9ad",
"module": "interface",
"function": "register_market",
"type_arguments": [
"0x6f048d2b0929f0f2c16dd7ccf643b59b7b029d180766780ba83ce9f847306715::usdc::USDC"
],
"arguments": [
{
"Input": 13
},
{
"Input": 14
},
{
"Result": 1
}
]
}
},
{
"MoveCall": {
"package": "0xb9f430cddd9cbe60f0beea60fce6da590688abbf2948dccff7cdda5a874be9ad",
"module": "interface",
"function": "share_clearing_house",
"type_arguments": [
"0x6f048d2b0929f0f2c16dd7ccf643b59b7b029d180766780ba83ce9f847306715::usdc::USDC"
],
"arguments": [
{
"Result": 1
}
]
}
}
]
},
"sender": "0x98e9cafb116af9d69f77ce0d644c60e384f850f8af050b268377d8293d7fe7c6",
"gasData": {
"payment": [
{
"objectId": "0x39e44ce0f8ee07f02d38c2f5fc7d9805bee241d0acbb2153e1e3fa005abf9736",
"version": 553,
"digest": "A1NSYfktRWLNCVBC6jHmRs5SyhkJYoo4CtNg7QZq3vZC"
}
],
"owner": "0x98e9cafb116af9d69f77ce0d644c60e384f850f8af050b268377d8293d7fe7c6",
"price": "1000",
"budget": "39172808"
}
},
"txSignatures": [
"ABfUuGXoWtGL54zCZh2Ef3NsNAHQqgibuIFUieVUox8EsTpNgH3WiKq/UgHwnlB3xW7D+AeC5hoBcVO6KbGPBAmvVqH0xXcgXDKTMc2JG4DUIii4K/ah/Is/TjelRccIlg=="
]
});
let block: SuiTransactionBlock = serde_json::from_value(value.clone()).unwrap();
let restored = serde_json::to_value(&block).unwrap();
assert_eq!(value, restored);
}
}