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{table}\n")?;
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{table}\n")?;
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:  {function} \n │ Module:    {module}\n │ Package:   {package}"
218        )?;
219
220        if !type_arguments.is_empty() {
221            write!(f, "\n │ Type Arguments: \n │   ")?;
222            write_sep(f, type_arguments, "\n │   ")?;
223        }
224        if !arguments.is_empty() {
225            write!(f, "\n │ Arguments: \n │   ")?;
226            write_sep(f, arguments.iter().map(Pretty), "\n │   ")?;
227        }
228
229        write!(f, "\n └")
230    }
231}
232
233impl Display for Pretty<'_, Argument> {
234    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
235        let Pretty(argument) = self;
236
237        let output = match argument {
238            Argument::GasCoin => "GasCoin".to_string(),
239            Argument::Input(i) => format!("Input  {i}"),
240            Argument::Result(i) => format!("Result {i}"),
241            Argument::NestedResult(j, k) => format!("Result {j}: {k}"),
242        };
243        write!(f, "{output}")
244    }
245}
246impl Display for Pretty<'_, ResolvedResults> {
247    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
248        let Pretty(ResolvedResults {
249            mutable_reference_outputs,
250            return_values,
251        }) = self;
252
253        let len_m_ref = mutable_reference_outputs.len();
254        let len_ret_vals = return_values.len();
255
256        if len_ret_vals > 0 {
257            write!(f, "\n Return Values:\n ──────────────")?;
258        }
259
260        for (i, value) in return_values.iter().enumerate() {
261            write!(f, "\n • Result {i:<2} ")?;
262            write!(f, "\n{value:#}\n")?;
263        }
264
265        if len_m_ref > 0 {
266            write!(
267                f,
268                "\n Mutable Reference Outputs:\n ──────────────────────────"
269            )?;
270        }
271
272        for (arg, value) in mutable_reference_outputs {
273            write!(f, "\n • {arg} ")?;
274            write!(f, "\n{value:#}\n")?;
275        }
276
277        if len_ret_vals == 0 && len_m_ref == 0 {
278            write!(f, "\n No return values")?;
279        }
280
281        Ok(())
282    }
283}
284
285impl Display for Pretty<'_, TypeTag> {
286    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287        let Pretty(type_tag) = self;
288        match type_tag {
289            TypeTag::Vector(v) => {
290                write!(f, "Vector of {}", Pretty(&**v))
291            }
292            TypeTag::Struct(s) => {
293                write!(f, "{}::{}", s.module, s.name)
294            }
295            _ => {
296                write!(f, "{type_tag}")
297            }
298        }
299    }
300}
301
302fn resolve_to_layout(
303    type_tag: &TypeTag,
304    executor: &Arc<dyn Executor + Send + Sync>,
305    store_factory: &LocalExec,
306) -> MoveTypeLayout {
307    match type_tag {
308        TypeTag::Vector(inner) => {
309            MoveTypeLayout::Vector(Box::from(resolve_to_layout(inner, executor, store_factory)))
310        }
311        TypeTag::Struct(inner) => {
312            let mut layout_resolver = executor.type_layout_resolver(Box::new(store_factory));
313            layout_resolver
314                .get_annotated_layout(inner)
315                .unwrap()
316                .into_layout()
317        }
318        TypeTag::Bool => MoveTypeLayout::Bool,
319        TypeTag::U8 => MoveTypeLayout::U8,
320        TypeTag::U64 => MoveTypeLayout::U64,
321        TypeTag::U128 => MoveTypeLayout::U128,
322        TypeTag::Address => MoveTypeLayout::Address,
323        TypeTag::Signer => MoveTypeLayout::Signer,
324        TypeTag::U16 => MoveTypeLayout::U16,
325        TypeTag::U32 => MoveTypeLayout::U32,
326        TypeTag::U256 => MoveTypeLayout::U256,
327    }
328}
329
330fn resolve_value(
331    bytes: &[u8],
332    type_tag: &TypeTag,
333    executor: &Arc<dyn Executor + Send + Sync>,
334    store_factory: &LocalExec,
335) -> anyhow::Result<MoveValue> {
336    let layout = resolve_to_layout(type_tag, executor, store_factory);
337    BoundedVisitor::deserialize_value(bytes, &layout)
338}
339
340pub fn transform_command_results_to_annotated(
341    executor: &Arc<dyn Executor + Send + Sync>,
342    store_factory: &LocalExec,
343    results: Vec<ExecutionResult>,
344) -> anyhow::Result<Vec<ResolvedResults>> {
345    let mut output = Vec::new();
346    for (m_refs, return_vals) in results.iter() {
347        let mut m_refs_out = Vec::new();
348        let mut return_vals_out = Vec::new();
349        for (arg, bytes, tag) in m_refs {
350            m_refs_out.push((*arg, resolve_value(bytes, tag, executor, store_factory)?));
351        }
352        for (bytes, tag) in return_vals {
353            return_vals_out.push(resolve_value(bytes, tag, executor, store_factory)?);
354        }
355        output.push(ResolvedResults {
356            mutable_reference_outputs: m_refs_out,
357            return_values: return_vals_out,
358        });
359    }
360    Ok(output)
361}