Skip to content

Commit

Permalink
✨ feat: add help TUI view, update command_palette TUI view (#132)
Browse files Browse the repository at this point in the history
* ✨ feat: add snapshot information to `command_palette` TUI view

* ✨ feat: add help TUI view back
  • Loading branch information
Jon-Becker authored Aug 12, 2023
1 parent 4322bfb commit 990f84d
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 17 deletions.
12 changes: 4 additions & 8 deletions heimdall/src/snapshot/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,18 @@ lazy_static! {
pub static ref ABOUT_TEXT: Vec<String> = vec![
format!("heimdall-rs v{}", env!("CARGO_PKG_VERSION")),
"By Jonathan Becker <[email protected]>".to_string(),
"The storage dump module will fetch all storage slots and values accessed by any EVM contract.".to_string(),
"The snapshot module allows users to quickly generate an overview of a contract's bytecode, without the need for the contract's source code.".to_string(),
];

pub static ref HELP_MENU_COMMANDS: Vec<String> = vec![
":q, :quit exit the program".to_string(),
":h, :help display this help menu".to_string(),
":f, :find <VALUE> search for a storage slot by slot or value".to_string(),
":e, :export <FILENAME> export the current storage dump to a file, preserving decoded values".to_string(),
":s, :seek <DIRECTION> <AMOUNT> move the cusor up or down by a specified amount".to_string(),
];

pub static ref HELP_MENU_CONTROLS: Vec<String> = vec![
"↑, Scroll Up move the cursor up one slot".to_string(),
"↓, Scroll Down move the cursor down one slot".to_string(),
"←, → change the decoding type of the selected slot".to_string(),
"CTRL + ↑, CTRL + ↓ move the cursor up or down by 10 slots".to_string(),
"↑, Scroll Up move the cursor up".to_string(),
"↓, Scroll Down move the cursor down".to_string(),
"←, → switch scrolling context between selector list and snapshot information".to_string(),
"ESC clear the search filter".to_string(),
];

Expand Down
274 changes: 270 additions & 4 deletions heimdall/src/snapshot/menus/command_palette.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,292 @@
use heimdall_common::utils::strings::encode_hex_reduced;
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout},
style::{Color, Style},
widgets::{Block, Borders, Paragraph},
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, Paragraph, Table},
Frame,
};

use crate::snapshot::structures::state::State;
use crate::snapshot::{structures::state::State, util::table::build_rows};

pub fn render_tui_command_palette<B: Backend>(f: &mut Frame<B>, state: &mut State) {
// creates a new block with the given title
// https://github.com/fdehau/tui-rs/blob/master/examples/paragraph.rs
let create_block = |title, borders| {
Block::default()
.borders(borders)
.style(Style::default().fg(Color::White))
.title(Span::styled(title, Style::default().add_modifier(Modifier::BOLD)))
};

// build main layout
let main_layout = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints([Constraint::Length(3), Constraint::Percentage(100)].as_ref())
.split(f.size());

let sub_layout = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Length(14), Constraint::Percentage(100)].as_ref())
.split(main_layout[1]);

let detail_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(100)].as_ref())
.split(sub_layout[1]);

// add command paragraph input
let input_buffer = state.input_buffer.clone();
let command_input = Paragraph::new(input_buffer)
.style(Style::default().fg(Color::White))
.block(Block::default().title(" Command ").borders(Borders::ALL));

// build rows
let rows = build_rows(state, main_layout[1].height as usize - 4);

// build table
let table = Table::new(rows)
.block(
Block::default()
.title(" Selectors ")
.style(Style::default().fg(Color::White).add_modifier(Modifier::BOLD))
.borders(Borders::ALL),
)
.widths(&[Constraint::Length(12), Constraint::Length(14), Constraint::Percentage(100)]);

// build function info
let snapshot = state.snapshots.get(state.function_index).unwrap();

// build modifiers
let modifiers = vec![
if snapshot.payable { "payable" } else { "" },
if snapshot.pure { "pure" } else { "" },
if snapshot.view && !snapshot.pure { "view" } else { "" },
]
.iter()
.filter(|x| !x.is_empty())
.map(|x| x.to_string())
.collect::<Vec<_>>();

// build argument list
let mut arg_strings: Vec<String> = Vec::new();
match &snapshot.resolved_function {
Some(function) => {
for (index, input) in function.inputs.iter().enumerate() {
arg_strings.push(format!("arg{} {}", index, input));
}
}
None => {
let mut sorted_arguments: Vec<_> = snapshot.arguments.clone().into_iter().collect();
sorted_arguments.sort_by(|x, y| x.0.cmp(&y.0));
for (index, (_, solidity_type)) in sorted_arguments {
arg_strings.push(format!("arg{} {}", index, solidity_type.first().unwrap()));
}
}
};

// add function resolved name
let mut text = vec![
Spans::from(""), // buffer
Spans::from(Span::styled(
" Function ",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
Spans::from(match &snapshot.resolved_function {
Some(function) => format!(" {}({})", function.name, arg_strings.join(", ")),
None => format!(" Unresolved_{}()", snapshot.selector),
}),
];

// build function snapshot
text.append(&mut vec![
// add modifiers and arguments
Spans::from(""), // buffer
Spans::from(Span::styled(
" Modifiers Returns Entry Point Branch Count",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
Spans::from(format!(
" {:<16}{:<15}{:<17}{}",
modifiers.join(" "),
snapshot.returns.clone().unwrap_or("None".to_owned()),
snapshot.entry_point,
snapshot.branch_count
)),
]);

// add gas consumptions
text.append(&mut vec![
// add modifiers and arguments
Spans::from(""), // buffer
Spans::from(Span::styled(
" Minimum Gas Consumed Maximum Gas Consumed Average Gas Consumed",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
Spans::from(format!(
" {:<24}{:<25}{}",
snapshot.gas_used.min, snapshot.gas_used.max, snapshot.gas_used.avg
)),
]);

// add events
if !snapshot.events.is_empty() {
text.append(&mut vec![
Spans::from(""), // buffer
Spans::from(Span::styled(
" Events ",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
]);
text.append(
&mut snapshot
.events
.iter()
.map(|x| {
let key = encode_hex_reduced(*x.0).replacen("0x", "", 1);
match state.resolved_events.get(&key) {
Some(event) => {
Spans::from(format!(" {}({})", event.name, event.inputs.join(",")))
}
None => Spans::from(format!(" Event_{}()", key[0..8].to_owned())),
}
})
.collect::<Vec<_>>(),
);
}

// add errors
if !snapshot.errors.is_empty() {
text.append(&mut vec![
Spans::from(""), // buffer
Spans::from(Span::styled(
" Errors ",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
]);
text.append(
&mut snapshot
.errors
.iter()
.map(|x| {
let key = encode_hex_reduced(*x.0).replacen("0x", "", 1);
match state.resolved_errors.get(&key) {
Some(error) => {
Spans::from(format!(" {}({})", error.name, error.inputs.join(",")))
}
None => Spans::from(format!(" Error_{}()", key[0..8].to_owned())),
}
})
.collect::<Vec<_>>(),
);
}

// add external calls
if !snapshot.external_calls.is_empty() {
text.append(&mut vec![
Spans::from(""), // buffer
Spans::from(Span::styled(
" External Calls ",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
]);
text.append(
&mut snapshot
.external_calls
.iter()
.map(|x| Spans::from(format!(" {}", x)))
.collect::<Vec<_>>(),
);
}

// add strings
if !snapshot.strings.is_empty() {
text.append(&mut vec![
Spans::from(""), // buffer
Spans::from(Span::styled(
" Strings ",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
]);
text.append(
&mut snapshot
.strings
.iter()
.map(|x| Spans::from(format!(" {}", x)))
.collect::<Vec<_>>(),
);
}

// add addresses
if !snapshot.addresses.is_empty() {
text.append(&mut vec![
Spans::from(""), // buffer
Spans::from(Span::styled(
" Hardcoded Addresses ",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
]);
text.append(
&mut snapshot
.addresses
.iter()
.map(|x| Spans::from(format!(" {}", x)))
.collect::<Vec<_>>(),
);
}

// add storage
if !snapshot.storage.is_empty() {
text.append(&mut vec![
Spans::from(""), // buffer
Spans::from(Span::styled(
" Storage ",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
]);
text.append(
&mut snapshot
.storage
.iter()
.map(|x| Spans::from(format!(" {}", x)))
.collect::<Vec<_>>(),
);
}

// add control statements
if !snapshot.control_statements.is_empty() {
text.append(&mut vec![
Spans::from(""), // buffer
Spans::from(Span::styled(
" Control Statements ",
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
)),
]);
text.append(
&mut snapshot
.control_statements
.iter()
.map(|x| Spans::from(format!(" {}", x)))
.collect::<Vec<_>>(),
);
}

// about text
let snapshot_header = format!(
" {}Snapshot of 0x{}{} ",
if state.scroll { "> " } else { "" },
snapshot.selector,
if state.scroll { " <" } else { "" },
);
let function_snapshot = Paragraph::new(text)
.style(Style::default().fg(Color::White))
.block(create_block(snapshot_header, Borders::ALL))
.alignment(Alignment::Left)
.scroll((state.scroll_index as u16, 0));

f.render_widget(command_input, main_layout[0]);
f.render_widget(table, sub_layout[0]);
f.render_widget(function_snapshot, detail_layout[0]);
}
13 changes: 8 additions & 5 deletions heimdall/src/snapshot/menus/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use tui::{
Frame,
};

use crate::snapshot::structures::state::State;
use crate::snapshot::{
constants::{ABOUT_TEXT, HELP_MENU_COMMANDS, HELP_MENU_CONTROLS},
structures::state::State,
};

pub fn render_tui_help<B: Backend>(f: &mut Frame<B>, _: &mut State) {
// build main layout
Expand All @@ -17,7 +20,7 @@ pub fn render_tui_help<B: Backend>(f: &mut Frame<B>, _: &mut State) {
.constraints(
[
Constraint::Length(6),
Constraint::Length(2.try_into().unwrap()),
Constraint::Length((HELP_MENU_COMMANDS.len() + 2).try_into().unwrap()),
Constraint::Percentage(100),
]
.as_ref(),
Expand All @@ -34,23 +37,23 @@ pub fn render_tui_help<B: Backend>(f: &mut Frame<B>, _: &mut State) {
};

// about text
let paragraph = Paragraph::new("")
let paragraph = Paragraph::new(ABOUT_TEXT.join("\n"))
.style(Style::default().fg(Color::White))
.block(create_block("About"))
.alignment(Alignment::Left)
.wrap(Wrap { trim: true });
f.render_widget(paragraph, main_layout[0]);

// commands paragraph
let paragraph = Paragraph::new("")
let paragraph = Paragraph::new(HELP_MENU_COMMANDS.join("\n"))
.style(Style::default().fg(Color::White))
.block(create_block("Commands"))
.alignment(Alignment::Left)
.wrap(Wrap { trim: true });
f.render_widget(paragraph, main_layout[1]);

// controls paragraph
let paragraph = Paragraph::new("")
let paragraph = Paragraph::new(HELP_MENU_CONTROLS.join("\n"))
.style(Style::default().fg(Color::White))
.block(create_block("Controls"))
.alignment(Alignment::Left)
Expand Down
2 changes: 2 additions & 0 deletions heimdall/src/snapshot/menus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use tui::{backend::Backend, Frame};
use super::structures::state::State;

pub mod command_palette;
pub mod help;
pub mod main;

#[derive(Debug, Clone, PartialEq, Eq)]
Expand All @@ -19,6 +20,7 @@ pub fn render_ui<B: Backend>(f: &mut Frame<B>, state: &mut State) {
match state.view {
TUIView::Main => main::render_tui_view_main(f, state),
TUIView::CommandPalette => command_palette::render_tui_command_palette(f, state),
TUIView::Help => help::render_tui_help(f, state),
_ => {}
}
}

0 comments on commit 990f84d

Please sign in to comment.