Skip to main content

iota_replay/displays/
transaction_displays.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    fmt::{Display, Formatter},
7    sync::Arc,
8};
9
10use iota_execution::Executor;
11use iota_sdk_types::{Argument, Command, MoveCall, ProgrammableTransaction, TypeTag};
12use iota_types::{
13    execution::ExecutionResult, object::bounded_visitor::BoundedVisitor, transaction::CallArg,
14};
15use move_core_types::annotated_value::{MoveTypeLayout, MoveValue};
16use tabled::{
17    builder::Builder as TableBuilder,
18    settings::{Panel as TablePanel, Style as TableStyle, style::HorizontalLine},
19};
20
21use crate::{
22    displays::{Pretty, write_sep},
23    replay::LocalExec,
24};
25
26pub struct FullPTB {
27    pub ptb: ProgrammableTransaction,
28    pub results: Vec<ResolvedResults>,
29}
30
31pub struct ResolvedResults {
32    pub mutable_reference_outputs: Vec<(Argument, MoveValue)>,
33    pub return_values: Vec<MoveValue>,
34}
35
36/// These Display implementations provide alternate displays that are used to
37/// format info contained in these Structs when calling the CLI replay command
38/// with an additional provided flag.
39impl Display for Pretty<'_, FullPTB> {
40    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41        let Pretty(full_ptb) = self;
42        let FullPTB { ptb, results } = full_ptb;
43
44        let ProgrammableTransaction { inputs, commands } = ptb;
45
46        // write input objects section
47        if !inputs.is_empty() {
48            let mut builder = TableBuilder::default();
49            for (i, input) in inputs.iter().enumerate() {
50                match input {
51                    CallArg::Pure(v) => {
52                        if v.len() <= 16 {
53                            builder.push_record(vec![format!("{i:<3} Pure Arg          {:?}", v)]);
54                        } else {
55                            builder.push_record(vec![format!(
56                            "{i:<3} Pure Arg          [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, ...]",
57                            v[0],
58                            v[1],
59                            v[2],
60                            v[3],
61                            v[4],
62                            v[5],
63                            v[6],
64                            v[7],
65                            v[8],
66                            v[9],
67                            v[10],
68                            v[11],
69                            v[12],
70                            v[13],
71                            v[14],
72                        )]);
73                        }
74                    }
75
76                    CallArg::ImmutableOrOwned(o) => {
77                        builder.push_record(vec![format!(
78                            "{i:<3} Imm/Owned Object  ID: {}",
79                            o.object_id
80                        )]);
81                    }
82                    CallArg::Shared(obj_ref) => {
83                        builder.push_record(vec![format!(
84                            "{i:<3} Shared Object     ID: {}",
85                            obj_ref.object_id
86                        )]);
87                    }
88                    CallArg::Receiving(o) => {
89                        builder.push_record(vec![format!(
90                            "{i:<3} Receiving Object  ID: {}",
91                            o.object_id
92                        )]);
93                    }
94                    _ => unimplemented!(
95                        "a new CallArg enum variant was added and needs to be handled"
96                    ),
97                };
98            }
99
100            let mut table = builder.build();
101            table.with(TablePanel::header("Input Objects"));
102            table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
103                1,
104                TableStyle::modern().get_horizontal(),
105            )]));
106            write!(f, "\n{table}\n")?;
107        } else {
108            write!(f, "\n  No input objects for this transaction")?;
109        }
110
111        // write command results section
112        if !results.is_empty() {
113            write!(f, "\n\n")?;
114        }
115        for (i, result) in results.iter().enumerate() {
116            if i == results.len() - 1 {
117                write!(
118                    f,
119                    "╭───────────────────╮\n│ Command {i:<2} Output │\n╰───────────────────╯{}\n\n\n",
120                    Pretty(result)
121                )?
122            } else {
123                write!(
124                    f,
125                    "╭───────────────────╮\n│ Command {i:<2} Output │\n╰───────────────────╯{}\n",
126                    Pretty(result)
127                )?
128            }
129        }
130
131        // write ptb functions section
132        let mut builder = TableBuilder::default();
133        if !commands.is_empty() {
134            for (i, c) in commands.iter().enumerate() {
135                if i == commands.len() - 1 {
136                    builder.push_record(vec![format!("{i:<2} {}", Pretty(c))]);
137                } else {
138                    builder.push_record(vec![format!("{i:<2} {}\n", Pretty(c))]);
139                }
140            }
141
142            let mut table = builder.build();
143            table.with(TablePanel::header("Commands"));
144            table.with(TableStyle::rounded().horizontals([HorizontalLine::new(
145                1,
146                TableStyle::modern().get_horizontal(),
147            )]));
148            write!(f, "\n{table}\n")?;
149        } else {
150            write!(f, "\n  No commands for this transaction")?;
151        }
152
153        Ok(())
154    }
155}
156
157impl Display for Pretty<'_, Command> {
158    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
159        let Pretty(command) = self;
160        match command {
161            Command::MoveCall(p) => {
162                write!(f, "{}", Pretty(p))
163            }
164            Command::MakeMoveVector(cmd) => {
165                write!(f, "MakeMoveVector:\n ┌")?;
166                if let Some(ty) = &cmd.type_tag {
167                    write!(f, "\n │ Type Tag: {ty}")?;
168                }
169                write!(f, "\n │ Arguments:\n │   ")?;
170                write_sep(f, cmd.elements.iter().map(Pretty), None, "\n │   ")?;
171                write!(f, "\n └")
172            }
173            Command::TransferObjects(cmd) => {
174                write!(f, "TransferObjects:\n ┌\n │ Arguments: \n │   ")?;
175                write_sep(f, cmd.objects.iter().map(Pretty), None, "\n │   ")?;
176                write!(f, "\n │ Address: {}\n └", Pretty(&cmd.address))
177            }
178            Command::SplitCoins(cmd) => {
179                write!(
180                    f,
181                    "SplitCoins:\n ┌\n │ Coin: {}\n │ Amounts: \n │   ",
182                    Pretty(&cmd.coin)
183                )?;
184                write_sep(f, cmd.amounts.iter().map(Pretty), None, "\n │   ")?;
185                write!(f, "\n └")
186            }
187            Command::MergeCoins(cmd) => {
188                write!(
189                    f,
190                    "MergeCoins:\n ┌\n │ Target: {}\n │ Coins: \n │   ",
191                    Pretty(&cmd.coin)
192                )?;
193                write_sep(f, cmd.coins_to_merge.iter().map(Pretty), None, "\n │   ")?;
194                write!(f, "\n └")
195            }
196            Command::Publish(cmd) => {
197                write!(f, "Publish:\n ┌\n │ Dependencies: \n │   ")?;
198                write_sep(f, &cmd.dependencies, None, "\n │   ")?;
199                write!(f, "\n └")
200            }
201            Command::Upgrade(cmd) => {
202                write!(f, "Upgrade:\n ┌\n │ Dependencies: \n │   ")?;
203                write_sep(f, &cmd.dependencies, None, "\n │   ")?;
204                write!(f, "\n │ Current Package ID: {}", cmd.package)?;
205                write!(f, "\n │ Ticket: {}", Pretty(&cmd.ticket))?;
206                write!(f, "\n └")
207            }
208            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
209        }
210    }
211}
212
213impl Display for Pretty<'_, MoveCall> {
214    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
215        let Pretty(move_call) = self;
216        let MoveCall {
217            package,
218            module,
219            function,
220            type_arguments,
221            arguments,
222        } = move_call;
223
224        write!(
225            f,
226            "MoveCall:\n ┌\n │ Function:  {function} \n │ Module:    {module}\n │ Package:   {package}"
227        )?;
228
229        if !type_arguments.is_empty() {
230            write!(f, "\n │ Type Arguments: \n │   ")?;
231            write_sep(f, type_arguments, None, "\n │   ")?;
232        }
233        if !arguments.is_empty() {
234            write!(f, "\n │ Arguments: \n │   ")?;
235            write_sep(f, arguments.iter().map(Pretty), None, "\n │   ")?;
236        }
237
238        write!(f, "\n └")
239    }
240}
241
242impl Display for Pretty<'_, Argument> {
243    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
244        let Pretty(argument) = self;
245
246        let output = match argument {
247            Argument::Gas => "Gas".to_string(),
248            Argument::Input(i) => format!("Input  {i}"),
249            Argument::Result(i) => format!("Result {i}"),
250            Argument::NestedResult(j, k) => format!("Result {j}: {k}"),
251            _ => unimplemented!("a new Argument enum variant was added and needs to be handled"),
252        };
253        write!(f, "{output}")
254    }
255}
256impl Display for Pretty<'_, ResolvedResults> {
257    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
258        let Pretty(ResolvedResults {
259            mutable_reference_outputs,
260            return_values,
261        }) = self;
262
263        let len_m_ref = mutable_reference_outputs.len();
264        let len_ret_vals = return_values.len();
265
266        if len_ret_vals > 0 {
267            write!(f, "\n Return Values:\n ──────────────")?;
268        }
269
270        for (i, value) in return_values.iter().enumerate() {
271            write!(f, "\n • Result {i:<2} ")?;
272            write!(f, "\n{value:#}\n")?;
273        }
274
275        if len_m_ref > 0 {
276            write!(
277                f,
278                "\n Mutable Reference Outputs:\n ──────────────────────────"
279            )?;
280        }
281
282        for (arg, value) in mutable_reference_outputs {
283            write!(f, "\n • {arg} ")?;
284            write!(f, "\n{value:#}\n")?;
285        }
286
287        if len_ret_vals == 0 && len_m_ref == 0 {
288            write!(f, "\n No return values")?;
289        }
290
291        Ok(())
292    }
293}
294
295impl Display for Pretty<'_, TypeTag> {
296    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
297        let Pretty(type_tag) = self;
298        match type_tag {
299            TypeTag::Vector(v) => {
300                write!(f, "Vector of {}", Pretty(&**v))
301            }
302            TypeTag::Struct(s) => {
303                write!(f, "{}::{}", s.module(), s.name())
304            }
305            _ => {
306                write!(f, "{type_tag}")
307            }
308        }
309    }
310}
311
312fn resolve_to_layout(
313    type_tag: &TypeTag,
314    executor: &Arc<dyn Executor + Send + Sync>,
315    store_factory: &LocalExec,
316) -> MoveTypeLayout {
317    match type_tag {
318        TypeTag::Vector(inner) => {
319            MoveTypeLayout::Vector(Box::from(resolve_to_layout(inner, executor, store_factory)))
320        }
321        TypeTag::Struct(inner) => {
322            let mut layout_resolver = executor.type_layout_resolver(Box::new(store_factory));
323            layout_resolver
324                .get_annotated_layout(inner)
325                .unwrap()
326                .into_layout()
327        }
328        TypeTag::Bool => MoveTypeLayout::Bool,
329        TypeTag::U8 => MoveTypeLayout::U8,
330        TypeTag::U64 => MoveTypeLayout::U64,
331        TypeTag::U128 => MoveTypeLayout::U128,
332        TypeTag::Address => MoveTypeLayout::Address,
333        TypeTag::Signer => MoveTypeLayout::Signer,
334        TypeTag::U16 => MoveTypeLayout::U16,
335        TypeTag::U32 => MoveTypeLayout::U32,
336        TypeTag::U256 => MoveTypeLayout::U256,
337    }
338}
339
340fn resolve_value(
341    bytes: &[u8],
342    type_tag: &TypeTag,
343    executor: &Arc<dyn Executor + Send + Sync>,
344    store_factory: &LocalExec,
345) -> anyhow::Result<MoveValue> {
346    let layout = resolve_to_layout(type_tag, executor, store_factory);
347    BoundedVisitor::deserialize_value(bytes, &layout)
348}
349
350pub fn transform_command_results_to_annotated(
351    executor: &Arc<dyn Executor + Send + Sync>,
352    store_factory: &LocalExec,
353    results: Vec<ExecutionResult>,
354) -> anyhow::Result<Vec<ResolvedResults>> {
355    let mut output = Vec::new();
356    for (m_refs, return_vals) in results.iter() {
357        let mut m_refs_out = Vec::new();
358        let mut return_vals_out = Vec::new();
359        for (arg, bytes, tag) in m_refs {
360            m_refs_out.push((*arg, resolve_value(bytes, tag, executor, store_factory)?));
361        }
362        for (bytes, tag) in return_vals {
363            return_vals_out.push(resolve_value(bytes, tag, executor, store_factory)?);
364        }
365        output.push(ResolvedResults {
366            mutable_reference_outputs: m_refs_out,
367            return_values: return_vals_out,
368        });
369    }
370    Ok(output)
371}