From 266bdf43c2bd33e421d395de14d269c59c1fb46f Mon Sep 17 00:00:00 2001 From: stefan-mysten <135084671+stefan-mysten@users.noreply.github.com> Date: Mon, 12 Feb 2024 09:53:03 -0800 Subject: [PATCH] [cli] Fix issues with showing the output for a transaction (#16190) (#16193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This fixes the panic that tabled throws when trying to show the output for a transaction that has no inputs or commands. (e.g., calling `client call --package 0x2 --module kiosk --function default`). ## Test Plan Existing tests, manual test. Before: ``` thread 'main' panicked at /Users/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tabled-0.15.0/src/grid/records/records_mut.rs:25:18: index out of bounds: the len is 0 but the index is 0 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 2024-02-11T03:33:37.781716Z ERROR telemetry_subscribers: panicked at /Users/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tabled-0.15.0/src/grid/records/records_mut.rs:25:18: index out of bounds: the len is 0 but the index is 0 panic.file="/Users/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tabled-0.15.0/src/grid/records/records_mut.rs" panic.line=25 panic.column=18 ``` After: ``` Transaction Digest: ANmDQptNpb72oGKgb4Wygq9uFDWLxJwPAo865e75kP8N ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │ Transaction Data │ ├─────────────────────────────────────────────────────────────────────────────────────────────┤ │ Sender: 0x0fe375fff0ee40d20c54a7f2478b9b5c7eaa3625b7611f9661ec5faefb4a6fea │ │ Gas Owner: 0x0fe375fff0ee40d20c54a7f2478b9b5c7eaa3625b7611f9661ec5faefb4a6fea │ │ Gas Budget: 1000000000 MIST │ │ Gas Price: 1000 MIST │ │ Gas Payment: │ │ ┌── │ │ │ ID: 0x1270f13fabe5a1622179827643e8d0989a689a2dfb2e0ff74e97685186159c73 │ │ │ Version: 17 │ │ │ Digest: 2DTL2j9YGvy92AUhd5ECzx3jGLCk98VA4muvWqu4A4xs │ │ └── │ │ │ │ Transaction Kind: Programmable │ │ No input objects for this transaction │ │ ╭──────────────────────────────────────────────────────────────────────────────────╮ │ │ │ Commands │ │ │ ├──────────────────────────────────────────────────────────────────────────────────┤ │ │ │ 0 MoveCall: │ │ │ │ ┌ │ │ │ │ │ Function: default │ │ │ │ │ Module: kiosk │ │ │ │ │ Package: 0x0000000000000000000000000000000000000000000000000000000000000002 │ │ │ │ └ │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────╯ │ │ │ │ Signatures: │ │ +OOZ8uwjS+XK3sz0TyMf19d6ouwLSfqiF57jSFxkH0KNJN2c9tDebN3JrqEHXV6wzON492THldd95oLDW+6UBA== │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ``` --- If your changes are not user-facing and do not break anything, you can skip the following section. Otherwise, please briefly describe what has changed under the Release Notes section. ### Type of Change (Check all that apply) - [ ] protocol change - [x] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes Fixed the CLI to handle the table output for a transaction that has no inputs or commands. (e.g., calling `client call --package 0x2 --module kiosk --function default`). --- .../src/displays/transaction_displays.rs | 108 ++++++++++-------- .../src/displays/transaction_displays.rs | 91 ++++++++------- 2 files changed, 109 insertions(+), 90 deletions(-) diff --git a/crates/sui-json-rpc-types/src/displays/transaction_displays.rs b/crates/sui-json-rpc-types/src/displays/transaction_displays.rs index e9e2e9babc1b0..ad37fd194e1f1 100644 --- a/crates/sui-json-rpc-types/src/displays/transaction_displays.rs +++ b/crates/sui-json-rpc-types/src/displays/transaction_displays.rs @@ -16,61 +16,75 @@ use tabled::{ impl<'a> Display for Pretty<'a, SuiProgrammableTransactionBlock> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut builder = TableBuilder::default(); - let Pretty(ptb) = self; let SuiProgrammableTransactionBlock { inputs, commands } = ptb; - - for (i, input) in inputs.iter().enumerate() { - match input { - SuiCallArg::Pure(v) => { - let pure_arg = if let Some(t) = v.value_type() { - format!( - "{i:<3} Pure Arg: Type: {}, Value: {}", - t, - v.value().to_json_value() - ) - } else { - format!("{i:<3} Pure Arg: {}", v.value().to_json_value()) - }; - builder.push_record(vec![pure_arg]); - } - SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject { object_id, .. }) => { - builder.push_record(vec![format!("{i:<3} Imm/Owned Object ID: {}", object_id)]); - } - SuiCallArg::Object(SuiObjectArg::SharedObject { object_id, .. }) => { - builder.push_record(vec![format!("{i:<3} Shared Object ID: {}", object_id)]); - } - SuiCallArg::Object(SuiObjectArg::Receiving { object_id, .. }) => { - builder.push_record(vec![format!("{i:<3} Receiving Object ID: {}", object_id)]); + if !inputs.is_empty() { + let mut builder = TableBuilder::default(); + for (i, input) in inputs.iter().enumerate() { + match input { + SuiCallArg::Pure(v) => { + let pure_arg = if let Some(t) = v.value_type() { + format!( + "{i:<3} Pure Arg: Type: {}, Value: {}", + t, + v.value().to_json_value() + ) + } else { + format!("{i:<3} Pure Arg: {}", v.value().to_json_value()) + }; + builder.push_record(vec![pure_arg]); + } + SuiCallArg::Object(SuiObjectArg::ImmOrOwnedObject { object_id, .. }) => { + builder.push_record(vec![format!( + "{i:<3} Imm/Owned Object ID: {}", + object_id + )]); + } + SuiCallArg::Object(SuiObjectArg::SharedObject { object_id, .. }) => { + builder.push_record(vec![format!( + "{i:<3} Shared Object ID: {}", + object_id + )]); + } + SuiCallArg::Object(SuiObjectArg::Receiving { object_id, .. }) => { + builder.push_record(vec![format!( + "{i:<3} Receiving Object ID: {}", + object_id + )]); + } } } + + let mut table = builder.build(); + table.with(TablePanel::header("Input Objects")); + table.with(TableStyle::rounded().horizontals([HorizontalLine::new( + 1, + TableStyle::modern().get_horizontal(), + )])); + write!(f, "\n{}", table)?; + } else { + write!(f, "\n No input objects for this transaction")?; } - let mut table = builder.build(); - table.with(TablePanel::header("Input Objects")); - table.with(TableStyle::rounded().horizontals([HorizontalLine::new( - 1, - TableStyle::modern().get_horizontal(), - )])); - write!(f, "\n{}", table)?; - - let mut builder = TableBuilder::default(); - - for (i, c) in commands.iter().enumerate() { - if i == commands.len() - 1 { - builder.push_record(vec![format!("{i:<2} {}", Pretty(c))]); - } else { - builder.push_record(vec![format!("{i:<2} {}\n", Pretty(c))]); + if !commands.is_empty() { + let mut builder = TableBuilder::default(); + for (i, c) in commands.iter().enumerate() { + if i == commands.len() - 1 { + builder.push_record(vec![format!("{i:<2} {}", Pretty(c))]); + } else { + builder.push_record(vec![format!("{i:<2} {}\n", Pretty(c))]); + } } + let mut table = builder.build(); + table.with(TablePanel::header("Commands")); + table.with(TableStyle::rounded().horizontals([HorizontalLine::new( + 1, + TableStyle::modern().get_horizontal(), + )])); + write!(f, "\n{}", table) + } else { + write!(f, "\n No commands for this transaction") } - let mut table = builder.build(); - table.with(TablePanel::header("Commands")); - table.with(TableStyle::rounded().horizontals([HorizontalLine::new( - 1, - TableStyle::modern().get_horizontal(), - )])); - write!(f, "\n{}", table) } } diff --git a/crates/sui-replay/src/displays/transaction_displays.rs b/crates/sui-replay/src/displays/transaction_displays.rs index 7c81afc36d640..70a498e16f7f8 100644 --- a/crates/sui-replay/src/displays/transaction_displays.rs +++ b/crates/sui-replay/src/displays/transaction_displays.rs @@ -16,18 +16,17 @@ use tabled::{ /// in these Structs when calling the CLI replay command with an additional provided flag. impl<'a> Display for Pretty<'a, ProgrammableTransaction> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut builder = TableBuilder::default(); - let Pretty(ptb) = self; let ProgrammableTransaction { inputs, commands } = ptb; - - for (i, input) in inputs.iter().enumerate() { - match input { - Pure(v) => { - if v.len() <= 16 { - builder.push_record(vec![format!("{i:<3} Pure Arg {:?}", v)]); - } else { - builder.push_record(vec![format!( + if !inputs.is_empty() { + let mut builder = TableBuilder::default(); + for (i, input) in inputs.iter().enumerate() { + match input { + Pure(v) => { + if v.len() <= 16 { + builder.push_record(vec![format!("{i:<3} Pure Arg {:?}", v)]); + } else { + builder.push_record(vec![format!( "{i:<3} Pure Arg [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, ...]", v[0], v[1], @@ -45,44 +44,50 @@ impl<'a> Display for Pretty<'a, ProgrammableTransaction> { v[13], v[14], )]); + } } - } - CallArg::Object(ObjectArg::ImmOrOwnedObject(o)) => { - builder.push_record(vec![format!("{i:<3} Imm/Owned Object ID: {}", o.0)]); - } - CallArg::Object(ObjectArg::SharedObject { id, .. }) => { - builder.push_record(vec![format!("{i:<3} Shared Object ID: {}", id)]); - } - CallArg::Object(ObjectArg::Receiving(o)) => { - builder.push_record(vec![format!("{i:<3} Receiving Object ID: {}", o.0)]); - } - }; - } - - let mut table = builder.build(); - table.with(TablePanel::header("Input Objects")); - table.with(TableStyle::rounded().horizontals([HorizontalLine::new( - 1, - TableStyle::modern().get_horizontal(), - )])); - write!(f, "\n{}\n", table)?; + CallArg::Object(ObjectArg::ImmOrOwnedObject(o)) => { + builder.push_record(vec![format!("{i:<3} Imm/Owned Object ID: {}", o.0)]); + } + CallArg::Object(ObjectArg::SharedObject { id, .. }) => { + builder.push_record(vec![format!("{i:<3} Shared Object ID: {}", id)]); + } + CallArg::Object(ObjectArg::Receiving(o)) => { + builder.push_record(vec![format!("{i:<3} Receiving Object ID: {}", o.0)]); + } + }; + } - let mut builder = TableBuilder::default(); + let mut table = builder.build(); + table.with(TablePanel::header("Input Objects")); + table.with(TableStyle::rounded().horizontals([HorizontalLine::new( + 1, + TableStyle::modern().get_horizontal(), + )])); + write!(f, "\n{}\n", table)?; + } else { + write!(f, "\n No input objects for this transaction")?; + } - for (i, c) in commands.iter().enumerate() { - if i == commands.len() - 1 { - builder.push_record(vec![format!("{i:<2} {}", Pretty(c))]); - } else { - builder.push_record(vec![format!("{i:<2} {}\n", Pretty(c))]); + if !commands.is_empty() { + let mut builder = TableBuilder::default(); + for (i, c) in commands.iter().enumerate() { + if i == commands.len() - 1 { + builder.push_record(vec![format!("{i:<2} {}", Pretty(c))]); + } else { + builder.push_record(vec![format!("{i:<2} {}\n", Pretty(c))]); + } } + let mut table = builder.build(); + table.with(TablePanel::header("Commands")); + table.with(TableStyle::rounded().horizontals([HorizontalLine::new( + 1, + TableStyle::modern().get_horizontal(), + )])); + write!(f, "\n{}\n", table) + } else { + write!(f, "\n No commands for this transaction") } - let mut table = builder.build(); - table.with(TablePanel::header("Commands")); - table.with(TableStyle::rounded().horizontals([HorizontalLine::new( - 1, - TableStyle::modern().get_horizontal(), - )])); - write!(f, "\n{}\n", table) } }