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