Skip to content

Commit ae0d074

Browse files
authored
Merge pull request #2213 from CosmWasm/debug-logs-cosmwasm-check
Add debug logs to cosmwasm-check
2 parents d20aca7 + 230d43b commit ae0d074

File tree

4 files changed

+150
-29
lines changed

4 files changed

+150
-29
lines changed

packages/check/src/main.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use clap::{Arg, ArgAction, Command};
88
use colored::Colorize;
99

1010
use cosmwasm_vm::capabilities_from_csv;
11-
use cosmwasm_vm::internals::{check_wasm, compile, make_compiling_engine};
11+
use cosmwasm_vm::internals::{
12+
check_wasm_with_logs, compile, make_compiling_engine, LogOutput, Logger,
13+
};
1214

1315
const DEFAULT_AVAILABLE_CAPABILITIES: &str =
1416
"iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1";
@@ -28,6 +30,13 @@ pub fn main() {
2830
.num_args(1)
2931
.action(ArgAction::Set),
3032
)
33+
.arg(
34+
Arg::new("VERBOSE")
35+
.long("verbose")
36+
.num_args(0)
37+
.help("Prints additional information on stderr")
38+
.action(ArgAction::SetTrue),
39+
)
3140
.arg(
3241
Arg::new("WASM")
3342
.help("Wasm file to read and compile")
@@ -54,7 +63,7 @@ pub fn main() {
5463

5564
let (passes, failures): (Vec<_>, _) = paths
5665
.map(|p| {
57-
let result = check_contract(p, &available_capabilities);
66+
let result = check_contract(p, &available_capabilities, matches.get_flag("VERBOSE"));
5867
match &result {
5968
Ok(_) => println!("{}: {}", p, "pass".green()),
6069
Err(e) => {
@@ -86,17 +95,32 @@ pub fn main() {
8695
}
8796

8897
fn check_contract(
89-
path: impl AsRef<Path>,
98+
path: &str,
9099
available_capabilities: &HashSet<String>,
100+
verbose: bool,
91101
) -> anyhow::Result<()> {
92102
let mut file = File::open(path)?;
93103

94104
// Read wasm
95105
let mut wasm = Vec::<u8>::new();
96106
file.read_to_end(&mut wasm)?;
97107

108+
// Potentially lossy filename or path as used as a short prefix for the output
109+
let filename_identifier: String = Path::new(path)
110+
.file_name()
111+
.map(|f| f.to_string_lossy().into_owned())
112+
.unwrap_or(path.to_string());
113+
let prefix = format!(" {}: ", filename_identifier);
114+
let logs = if verbose {
115+
Logger::On {
116+
prefix: &prefix,
117+
output: LogOutput::StdErr,
118+
}
119+
} else {
120+
Logger::Off
121+
};
98122
// Check wasm
99-
check_wasm(&wasm, available_capabilities)?;
123+
check_wasm_with_logs(&wasm, available_capabilities, logs)?;
100124

101125
// Compile module
102126
let engine = make_compiling_engine(None);

packages/check/tests/cosmwasm_check_tests.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ fn valid_contract_check() -> Result<(), Box<dyn std::error::Error>> {
1414
Ok(())
1515
}
1616

17+
#[test]
18+
fn contract_check_verbose() -> Result<(), Box<dyn std::error::Error>> {
19+
let mut cmd = Command::cargo_bin("cosmwasm-check")?;
20+
21+
cmd.arg("../vm/testdata/empty.wasm").arg("--verbose");
22+
cmd.assert()
23+
.success()
24+
.stdout(predicate::str::contains("pass"))
25+
.stderr(predicate::str::contains("Max function parameters"));
26+
27+
Ok(())
28+
}
29+
1730
#[test]
1831
fn empty_contract_check() -> Result<(), Box<dyn std::error::Error>> {
1932
let mut cmd = Command::cargo_bin("cosmwasm-check")?;

packages/vm/src/compatibility.rs

Lines changed: 108 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,64 @@ const MAX_TOTAL_FUNCTION_PARAMS: usize = 10_000;
9797
/// during static validation.
9898
const MAX_FUNCTION_RESULTS: usize = 1;
9999

100+
#[derive(Clone, Copy)]
101+
pub enum LogOutput {
102+
StdOut,
103+
StdErr,
104+
}
105+
#[derive(Clone, Copy, Default)]
106+
pub enum Logger<'a> {
107+
On {
108+
prefix: &'a str,
109+
output: LogOutput,
110+
},
111+
#[default]
112+
Off,
113+
}
114+
115+
impl<'a> Logger<'a> {
116+
pub fn with_config(output: LogOutput, prefix: &'a str) -> Self {
117+
On { output, prefix }
118+
}
119+
120+
/// Adds a message to the logs, if they are enabled.
121+
/// This is a convenience method for adding a single message.
122+
///
123+
/// Takes a closure that returns the message to add to avoid unnecessary allocations.
124+
pub fn add(&self, msg_fn: impl FnOnce() -> String) {
125+
if let On { prefix, output } = &self {
126+
let msg = msg_fn();
127+
match output {
128+
LogOutput::StdOut => println!("{prefix}{msg}"),
129+
LogOutput::StdErr => eprintln!("{prefix}{msg}"),
130+
}
131+
}
132+
}
133+
}
134+
135+
use Logger::*;
136+
100137
/// Checks if the data is valid wasm and compatibility with the CosmWasm API (imports and exports)
101138
pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) -> VmResult<()> {
139+
check_wasm_with_logs(wasm_code, available_capabilities, Off)
140+
}
141+
142+
pub fn check_wasm_with_logs(
143+
wasm_code: &[u8],
144+
available_capabilities: &HashSet<String>,
145+
logs: Logger<'_>,
146+
) -> VmResult<()> {
147+
logs.add(|| format!("Size of Wasm blob: {}", wasm_code.len()));
148+
102149
let mut module = ParsedWasm::parse(wasm_code)?;
103150

104151
check_wasm_tables(&module)?;
105152
check_wasm_memories(&module)?;
106153
check_interface_version(&module)?;
107-
check_wasm_exports(&module)?;
108-
check_wasm_imports(&module, SUPPORTED_IMPORTS)?;
109-
check_wasm_capabilities(&module, available_capabilities)?;
110-
check_wasm_functions(&module)?;
154+
check_wasm_exports(&module, logs)?;
155+
check_wasm_imports(&module, SUPPORTED_IMPORTS, logs)?;
156+
check_wasm_capabilities(&module, available_capabilities, logs)?;
157+
check_wasm_functions(&module, logs)?;
111158

112159
module.validate_funcs()
113160
}
@@ -188,8 +235,11 @@ fn check_interface_version(module: &ParsedWasm) -> VmResult<()> {
188235
}
189236
}
190237

191-
fn check_wasm_exports(module: &ParsedWasm) -> VmResult<()> {
238+
fn check_wasm_exports(module: &ParsedWasm, logs: Logger) -> VmResult<()> {
192239
let available_exports: HashSet<String> = module.exported_function_names(None);
240+
241+
logs.add(|| format!("Exports: {}", available_exports.to_string_limited(20_000)));
242+
193243
for required_export in REQUIRED_EXPORTS {
194244
if !available_exports.contains(*required_export) {
195245
return Err(VmError::static_validation_err(format!(
@@ -203,7 +253,24 @@ fn check_wasm_exports(module: &ParsedWasm) -> VmResult<()> {
203253
/// Checks if the import requirements of the contract are satisfied.
204254
/// When this is not the case, we either have an incompatibility between contract and VM
205255
/// or a error in the contract.
206-
fn check_wasm_imports(module: &ParsedWasm, supported_imports: &[&str]) -> VmResult<()> {
256+
fn check_wasm_imports(
257+
module: &ParsedWasm,
258+
supported_imports: &[&str],
259+
logs: Logger,
260+
) -> VmResult<()> {
261+
logs.add(|| {
262+
format!(
263+
"Imports ({}): {}",
264+
module.imports.len(),
265+
module
266+
.imports
267+
.iter()
268+
.map(|import| full_import_name(import))
269+
.collect::<Vec<_>>()
270+
.join(", ")
271+
)
272+
});
273+
207274
if module.imports.len() > MAX_IMPORTS {
208275
return Err(VmError::static_validation_err(format!(
209276
"Import count exceeds limit. Imports: {}. Limit: {}.",
@@ -240,8 +307,15 @@ fn full_import_name(ie: &Import) -> String {
240307
fn check_wasm_capabilities(
241308
module: &ParsedWasm,
242309
available_capabilities: &HashSet<String>,
310+
logs: Logger,
243311
) -> VmResult<()> {
244312
let required_capabilities = required_capabilities_from_module(module);
313+
logs.add(|| {
314+
format!(
315+
"Required capabilities: {}",
316+
required_capabilities.to_string_limited(20_000)
317+
)
318+
});
245319
if !required_capabilities.is_subset(available_capabilities) {
246320
// We switch to BTreeSet to get a sorted error message
247321
let unavailable: BTreeSet<_> = required_capabilities
@@ -255,7 +329,17 @@ fn check_wasm_capabilities(
255329
Ok(())
256330
}
257331

258-
fn check_wasm_functions(module: &ParsedWasm) -> VmResult<()> {
332+
fn check_wasm_functions(module: &ParsedWasm, logs: Logger) -> VmResult<()> {
333+
logs.add(|| format!("Function count: {}", module.function_count));
334+
logs.add(|| format!("Max function parameters: {}", module.max_func_params));
335+
logs.add(|| format!("Max function results: {}", module.max_func_results));
336+
logs.add(|| {
337+
format!(
338+
"Total function parameter count: {}",
339+
module.total_func_params
340+
)
341+
});
342+
259343
if module.function_count > MAX_FUNCTIONS {
260344
return Err(VmError::static_validation_err(format!(
261345
"Wasm contract contains more than {MAX_FUNCTIONS} functions"
@@ -599,7 +683,7 @@ mod tests {
599683
)
600684
.unwrap();
601685
let module = ParsedWasm::parse(&wasm).unwrap();
602-
check_wasm_exports(&module).unwrap();
686+
check_wasm_exports(&module, Off).unwrap();
603687

604688
// this is invalid, as it doesn't any required export
605689
let wasm = wat::parse_str(
@@ -611,7 +695,7 @@ mod tests {
611695
)
612696
.unwrap();
613697
let module = ParsedWasm::parse(&wasm).unwrap();
614-
match check_wasm_exports(&module) {
698+
match check_wasm_exports(&module, Off) {
615699
Err(VmError::StaticValidationErr { msg, .. }) => {
616700
assert!(msg.starts_with("Wasm contract doesn't have required export: \"allocate\""));
617701
}
@@ -630,7 +714,7 @@ mod tests {
630714
)
631715
.unwrap();
632716
let module = ParsedWasm::parse(&wasm).unwrap();
633-
match check_wasm_exports(&module) {
717+
match check_wasm_exports(&module, Off) {
634718
Err(VmError::StaticValidationErr { msg, .. }) => {
635719
assert!(
636720
msg.starts_with("Wasm contract doesn't have required export: \"deallocate\"")
@@ -660,7 +744,7 @@ mod tests {
660744
)"#,
661745
)
662746
.unwrap();
663-
check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS).unwrap();
747+
check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS, Off).unwrap();
664748
}
665749

666750
#[test]
@@ -771,8 +855,8 @@ mod tests {
771855
)"#,
772856
)
773857
.unwrap();
774-
let err =
775-
check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS).unwrap_err();
858+
let err = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS, Off)
859+
.unwrap_err();
776860
match err {
777861
VmError::StaticValidationErr { msg, .. } => {
778862
assert_eq!(msg, "Import count exceeds limit. Imports: 101. Limit: 100.");
@@ -809,7 +893,7 @@ mod tests {
809893
"env.debug",
810894
"env.query_chain",
811895
];
812-
let result = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), supported_imports);
896+
let result = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), supported_imports, Off);
813897
match result.unwrap_err() {
814898
VmError::StaticValidationErr { msg, .. } => {
815899
println!("{msg}");
@@ -825,7 +909,7 @@ mod tests {
825909
#[test]
826910
fn check_wasm_imports_of_old_contract() {
827911
let module = &ParsedWasm::parse(CONTRACT_0_7).unwrap();
828-
let result = check_wasm_imports(module, SUPPORTED_IMPORTS);
912+
let result = check_wasm_imports(module, SUPPORTED_IMPORTS, Off);
829913
match result.unwrap_err() {
830914
VmError::StaticValidationErr { msg, .. } => {
831915
assert!(
@@ -839,7 +923,7 @@ mod tests {
839923
#[test]
840924
fn check_wasm_imports_wrong_type() {
841925
let wasm = wat::parse_str(r#"(module (import "env" "db_read" (memory 1 1)))"#).unwrap();
842-
let result = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS);
926+
let result = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS, Off);
843927
match result.unwrap_err() {
844928
VmError::StaticValidationErr { msg, .. } => {
845929
assert!(
@@ -874,7 +958,7 @@ mod tests {
874958
]
875959
.into_iter()
876960
.collect();
877-
check_wasm_capabilities(&module, &available).unwrap();
961+
check_wasm_capabilities(&module, &available, Off).unwrap();
878962
}
879963

880964
#[test]
@@ -902,7 +986,7 @@ mod tests {
902986
]
903987
.into_iter()
904988
.collect();
905-
match check_wasm_capabilities(&module, &available).unwrap_err() {
989+
match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
906990
VmError::StaticValidationErr { msg, .. } => assert_eq!(
907991
msg,
908992
"Wasm contract requires unavailable capabilities: {\"sun\"}"
@@ -918,7 +1002,7 @@ mod tests {
9181002
]
9191003
.into_iter()
9201004
.collect();
921-
match check_wasm_capabilities(&module, &available).unwrap_err() {
1005+
match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
9221006
VmError::StaticValidationErr { msg, .. } => assert_eq!(
9231007
msg,
9241008
"Wasm contract requires unavailable capabilities: {\"sun\", \"water\"}"
@@ -928,7 +1012,7 @@ mod tests {
9281012

9291013
// Available set 3
9301014
let available = ["freedom".to_string()].into_iter().collect();
931-
match check_wasm_capabilities(&module, &available).unwrap_err() {
1015+
match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
9321016
VmError::StaticValidationErr { msg, .. } => assert_eq!(
9331017
msg,
9341018
"Wasm contract requires unavailable capabilities: {\"nutrients\", \"sun\", \"water\"}"
@@ -938,7 +1022,7 @@ mod tests {
9381022

9391023
// Available set 4
9401024
let available = [].into_iter().collect();
941-
match check_wasm_capabilities(&module, &available).unwrap_err() {
1025+
match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
9421026
VmError::StaticValidationErr { msg, .. } => assert_eq!(
9431027
msg,
9441028
"Wasm contract requires unavailable capabilities: {\"nutrients\", \"sun\", \"water\"}"
@@ -960,7 +1044,7 @@ mod tests {
9601044
.unwrap();
9611045
let module = ParsedWasm::parse(&wasm).unwrap();
9621046

963-
match check_wasm_functions(&module).unwrap_err() {
1047+
match check_wasm_functions(&module, Off).unwrap_err() {
9641048
VmError::StaticValidationErr { msg, .. } => assert_eq!(
9651049
msg,
9661050
"Wasm contract contains function with more than 100 parameters"
@@ -979,7 +1063,7 @@ mod tests {
9791063
))
9801064
.unwrap();
9811065
let module = ParsedWasm::parse(&wasm).unwrap();
982-
match check_wasm_functions(&module).unwrap_err() {
1066+
match check_wasm_functions(&module, Off).unwrap_err() {
9831067
VmError::StaticValidationErr { msg, .. } => assert_eq!(
9841068
msg,
9851069
"Wasm contract contains function with more than 1 results"
@@ -998,7 +1082,7 @@ mod tests {
9981082
))
9991083
.unwrap();
10001084
let module = ParsedWasm::parse(&wasm).unwrap();
1001-
match check_wasm_functions(&module).unwrap_err() {
1085+
match check_wasm_functions(&module, Off).unwrap_err() {
10021086
VmError::StaticValidationErr { msg, .. } => {
10031087
assert_eq!(msg, "Wasm contract contains more than 20000 functions")
10041088
}

packages/vm/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub mod internals {
5555
//! Please don't use any of these types directly, as
5656
//! they might change frequently or be removed in the future.
5757
58-
pub use crate::compatibility::check_wasm;
58+
pub use crate::compatibility::{check_wasm, check_wasm_with_logs, LogOutput, Logger};
5959
pub use crate::instance::instance_from_module;
6060
pub use crate::wasm_backend::{compile, make_compiling_engine, make_runtime_engine};
6161
}

0 commit comments

Comments
 (0)