Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/common/fmt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ serde_json.workspace = true
chrono.workspace = true
revm.workspace = true
yansi.workspace = true
comfy-table.workspace = true

# Tempo
tempo-alloy.workspace = true
Expand Down
107 changes: 107 additions & 0 deletions crates/common/fmt/src/console.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::UIfmt;
use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256};
use comfy_table::{Table, TableComponent, presets::UTF8_FULL};
use std::fmt::{self, Write};

/// A piece is a portion of the format string which represents the next part to emit.
Expand Down Expand Up @@ -407,6 +408,36 @@ fn format_spec<'a>(
}
}

pub fn console_table_format(
keys: Option<&[&dyn ConsoleFmt]>,
values: &[&dyn ConsoleFmt],
) -> String {
let keys_strings: Vec<String> = match keys {
Some(keys) => keys.iter().map(|k| k.fmt(FormatSpec::String)).collect(),
None => (0..values.len()).map(|i| i.to_string()).collect(),
};
let values_strings: Vec<String> = values.iter().map(|v| v.fmt(FormatSpec::String)).collect();

let mut table = Table::new();
table.load_preset(UTF8_FULL);
table.set_style(TableComponent::VerticalLines, '│');
table.set_style(TableComponent::HeaderLines, '─');
table.set_style(TableComponent::MiddleHeaderIntersections, '┼');
table.set_style(TableComponent::LeftHeaderIntersection, '├');
table.set_style(TableComponent::RightHeaderIntersection, '┤');
table.set_header(vec!["(index)", "Values"]);
table.remove_style(TableComponent::HorizontalLines);
table.remove_style(TableComponent::MiddleIntersections);
table.remove_style(TableComponent::LeftBorderIntersections);
table.remove_style(TableComponent::RightBorderIntersections);
for i in 0..keys_strings.len().max(values_strings.len()) {
let key = keys_strings.get(i).map(String::as_str).unwrap_or("");
let value = values_strings.get(i).map(String::as_str).unwrap_or("");
table.add_row(vec![key, value]);
}
table.to_string()
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -610,4 +641,80 @@ mod tests {
let call = Logs::Log1(log1);
assert_eq!(call.fmt(Default::default()), "foo 42 bar");
}

#[test]
fn test_console_table_format() {
// auto-indexed, uint256 values
let values: &[&dyn ConsoleFmt] = &[&U256::from(100), &U256::from(200), &U256::from(300)];
assert_eq!(
console_table_format(None, values),
"┌─────────┬────────┐\n\
│ (index) │ Values │\n\
├─────────┼────────┤\n\
│ 0 │ 100 │\n\
│ 1 │ 200 │\n\
│ 2 │ 300 │\n\
└─────────┴────────┘"
);

// string keys, uint256 values
// key col expands to fit "charlie123" and value col expands to fit "20000000000000000"
let keys: &[&dyn ConsoleFmt] =
&[&String::from("alice"), &String::from("bob"), &String::from("charlie123")];
let values: &[&dyn ConsoleFmt] = &[
&U256::from(1),
&U256::from_str("20000000000000000").unwrap(),
&U256::from_str("30000000000").unwrap(),
];
assert_eq!(
console_table_format(Some(keys), values),
"┌────────────┬───────────────────┐\n\
│ (index) │ Values │\n\
├────────────┼───────────────────┤\n\
│ alice │ 1 │\n\
│ bob │ 20000000000000000 │\n\
│ charlie123 │ 30000000000 │\n\
└────────────┴───────────────────┘"
);

// empty table
assert_eq!(
console_table_format(None, &[]),
"┌─────────┬────────┐\n\
│ (index) │ Values │\n\
├─────────┼────────┤\n\
└─────────┴────────┘"
);

// more keys than values
let keys: &[&dyn ConsoleFmt] =
&[&String::from("alice"), &String::from("bob"), &String::from("charlie")];
let values: &[&dyn ConsoleFmt] = &[&U256::from(1), &U256::from(2)];
assert_eq!(
console_table_format(Some(keys), values),
"┌─────────┬────────┐\n\
│ (index) │ Values │\n\
├─────────┼────────┤\n\
│ alice │ 1 │\n\
│ bob │ 2 │\n\
│ charlie │ │\n\
└─────────┴────────┘"
);

// more values than keys
let keys: &[&dyn ConsoleFmt] = &[&String::from("alice"), &String::from("bob")];
let values: &[&dyn ConsoleFmt] =
&[&U256::from(1), &U256::from(2), &U256::from(3), &U256::from(4)];
assert_eq!(
console_table_format(Some(keys), values),
"┌─────────┬────────┐\n\
│ (index) │ Values │\n\
├─────────┼────────┤\n\
│ alice │ 1 │\n\
│ bob │ 2 │\n\
│ │ 3 │\n\
│ │ 4 │\n\
└─────────┴────────┘"
);
}
}
2 changes: 1 addition & 1 deletion crates/common/fmt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]

mod console;
pub use console::{ConsoleFmt, FormatSpec, console_format};
pub use console::{ConsoleFmt, FormatSpec, console_format, console_table_format};

mod dynamic;
pub use dynamic::{
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/abi/src/Console.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions crates/evm/abi/src/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ def main():
# Parse signatures from `console.sol`'s string literals
console_sol = open(console_file).read()
sig_strings = re.findall(
r'"(log.*?)"',
r'"((?:log|table).*?)"',
console_sol,
)
raw_sigs = [s.strip().strip('"') for s in sig_strings]
sigs = [
s.replace("string", "string memory").replace("bytes)", "bytes memory)")
re.sub(r"(\w+\[\])", r"\1 memory", s)
.replace("string,", "string memory,")
.replace("string)", "string memory)")
.replace("bytes)", "bytes memory)")
for s in raw_sigs
]
sigs = list(set(sigs))
Expand All @@ -38,6 +41,7 @@ def main():
)
combined = json.loads(r.stdout.strip())
abi = combined["contracts"]["<stdin>:HardhatConsole"]["abi"]

open(abi_file, "w").write(json.dumps(abi, separators=(",", ":"), indent=None))


Expand Down
4 changes: 3 additions & 1 deletion crates/evm/evm/src/inspectors/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ impl LogCollector {

fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> {
let decoded = console::hh::ConsoleCalls::abi_decode(data)?;
self.push_msg(&decoded.fmt(Default::default()));
for line in decoded.fmt(Default::default()).lines() {
self.push_msg(line);
}
Ok(())
}

Expand Down
38 changes: 33 additions & 5 deletions crates/macros/src/console_fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use syn::{
pub fn console_fmt(input: &DeriveInput) -> TokenStream {
let name = &input.ident;
let tokens = match &input.data {
Data::Struct(s) => derive_struct(s),
Data::Struct(s) => derive_struct(s, name),
Data::Enum(e) => derive_enum(e),
Data::Union(_) => return quote!(compile_error!("Unions are unsupported");),
};
Expand All @@ -18,16 +18,16 @@ pub fn console_fmt(input: &DeriveInput) -> TokenStream {
}
}

fn derive_struct(s: &DataStruct) -> TokenStream {
let imp = impl_struct(s).unwrap_or_else(|| quote!(String::new()));
fn derive_struct(s: &DataStruct, name: &Ident) -> TokenStream {
let imp = impl_struct(s, name).unwrap_or_else(|| quote!(String::new()));
quote! {
fn fmt(&self, _spec: FormatSpec) -> String {
#imp
}
}
}

fn impl_struct(s: &DataStruct) -> Option<TokenStream> {
fn impl_struct(s: &DataStruct, name: &Ident) -> Option<TokenStream> {
if s.fields.is_empty() {
return None;
}
Expand All @@ -36,13 +36,41 @@ fn impl_struct(s: &DataStruct) -> Option<TokenStream> {
return None;
}

let members = s.fields.members().collect::<Vec<_>>();

// TODO: name-based dispatches may be fragile; find a better way to detect table structs
if name.to_string().starts_with("table") {
Comment thread
mablr marked this conversation as resolved.
Outdated
let member_ref = |m: &Member| match m {
Member::Named(ident) => quote!(&self.#ident),
Member::Unnamed(idx) => quote!(&self.#idx),
};
let imp = if members.len() == 1 {
let vals = member_ref(&members[0]);
quote! {
let values: ::std::vec::Vec<&dyn ConsoleFmt> =
(#vals).iter().map(|v| v as &dyn ConsoleFmt).collect();
console_table_format(None, &values)
}
} else {
let keys = member_ref(&members[0]);
let vals = member_ref(&members[1]);
quote! {
let keys: ::std::vec::Vec<&dyn ConsoleFmt> =
(#keys).iter().map(|v| v as &dyn ConsoleFmt).collect();
let values: ::std::vec::Vec<&dyn ConsoleFmt> =
(#vals).iter().map(|v| v as &dyn ConsoleFmt).collect();
console_table_format(Some(&keys), &values)
}
};
return Some(imp);
}

let fields = s.fields.iter().collect::<Vec<_>>();
let first_ty = match &fields.first().unwrap().ty {
Type::Path(path) => path.path.segments.last().unwrap().ident.to_string(),
_ => String::new(),
};

let members = s.fields.members().collect::<Vec<_>>();
let args: Punctuated<TokenStream, Token![,]> = members
.into_iter()
.map(|member| match member {
Expand Down
Loading