Skip to content

Commit

Permalink
Unfinished: Add debug logs to cosmwasm-check
Browse files Browse the repository at this point in the history
  • Loading branch information
webmaster128 committed Aug 13, 2024
1 parent 8f8f0ef commit 541d3ce
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 27 deletions.
9 changes: 7 additions & 2 deletions packages/check/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use clap::{Arg, ArgAction, Command};
use colored::Colorize;

use cosmwasm_vm::capabilities_from_csv;
use cosmwasm_vm::internals::{check_wasm, compile, make_compiling_engine};
use cosmwasm_vm::internals::{check_wasm_with_logs, compile, make_compiling_engine, Logs};

const DEFAULT_AVAILABLE_CAPABILITIES: &str =
"iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1";
Expand Down Expand Up @@ -95,8 +95,13 @@ fn check_contract(
let mut wasm = Vec::<u8>::new();
file.read_to_end(&mut wasm)?;

let logs = Logs::new();
// Check wasm
check_wasm(&wasm, available_capabilities)?;
let res = check_wasm_with_logs(&wasm, available_capabilities, logs.clone());
for line in logs.iter() {
eprintln!("{}", line);
}
res?;

// Compile module
let engine = make_compiling_engine(None);
Expand Down
167 changes: 143 additions & 24 deletions packages/vm/src/compatibility.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::collections::HashSet;
use std::iter::empty;
use std::rc::Rc;
use std::sync::Mutex;

use wasmer::wasmparser::Import;
use wasmer::wasmparser::TypeRef;
Expand Down Expand Up @@ -97,17 +101,62 @@ const MAX_TOTAL_FUNCTION_PARAMS: usize = 10_000;
/// during static validation.
const MAX_FUNCTION_RESULTS: usize = 1;

#[derive(Clone)]
pub enum Logs {
On(RefCell<Vec<String>>),
Off,
}

impl Logs {
pub fn new() -> Self {
On(RefCell::new(Vec::new()))
}

// Gets access to logs for writing
pub fn open(&mut self) -> Option<&mut Vec<String>> {
match self {
On(data) => {
let mut data = data.borrow_mut();
Some(data.as_mut())
}
Off => None,
}
}

pub fn iter(&self) -> impl Iterator<Item = String> {
let iter = match self {
On(data) => data.borrow().iter(),
Off => Vec::new().into_iter().into(), // How to create am empty Iter<String> ?!?!?!?
};
iter
}
}

use Logs::*;

/// Checks if the data is valid wasm and compatibility with the CosmWasm API (imports and exports)
pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) -> VmResult<()> {
check_wasm_with_logs(wasm_code, available_capabilities, Off)
}

pub fn check_wasm_with_logs(
wasm_code: &[u8],
available_capabilities: &HashSet<String>,
logs: Logs,
) -> VmResult<()> {
if let Some(logs) = logs.clone().open() {
logs.push(format!("Size of Wasm blob: {}", wasm_code.len()));
}

let mut module = ParsedWasm::parse(wasm_code)?;

check_wasm_tables(&module)?;
check_wasm_memories(&module)?;
check_interface_version(&module)?;
check_wasm_exports(&module)?;
check_wasm_imports(&module, SUPPORTED_IMPORTS)?;
check_wasm_capabilities(&module, available_capabilities)?;
check_wasm_functions(&module)?;
check_wasm_exports(&module, logs.clone())?;
check_wasm_imports(&module, SUPPORTED_IMPORTS, logs.clone())?;
check_wasm_capabilities(&module, available_capabilities, logs.clone())?;
check_wasm_functions(&module, logs.clone())?;

module.validate_funcs()
}
Expand Down Expand Up @@ -188,8 +237,16 @@ fn check_interface_version(module: &ParsedWasm) -> VmResult<()> {
}
}

fn check_wasm_exports(module: &ParsedWasm) -> VmResult<()> {
fn check_wasm_exports(module: &ParsedWasm, mut logs: Logs) -> VmResult<()> {
let available_exports: HashSet<String> = module.exported_function_names(None);

if let Some(logs) = logs.open() {
logs.push(format!(
"Exports: {}",
available_exports.to_string_limited(20_000)
));
}

for required_export in REQUIRED_EXPORTS {
if !available_exports.contains(*required_export) {
return Err(VmError::static_validation_err(format!(
Expand All @@ -203,7 +260,24 @@ fn check_wasm_exports(module: &ParsedWasm) -> VmResult<()> {
/// Checks if the import requirements of the contract are satisfied.
/// When this is not the case, we either have an incompatibility between contract and VM
/// or a error in the contract.
fn check_wasm_imports(module: &ParsedWasm, supported_imports: &[&str]) -> VmResult<()> {
fn check_wasm_imports(
module: &ParsedWasm,
supported_imports: &[&str],
mut logs: Logs,
) -> VmResult<()> {
if let Some(logs) = logs.open() {
logs.push(format!(
"Imports ({}): {}",
module.imports.len(),
module
.imports
.iter()
.map(|import| full_import_name(import))
.collect::<Vec<_>>()
.join(", ")
));
}

if module.imports.len() > MAX_IMPORTS {
return Err(VmError::static_validation_err(format!(
"Import count exceeds limit. Imports: {}. Limit: {}.",
Expand Down Expand Up @@ -240,8 +314,15 @@ fn full_import_name(ie: &Import) -> String {
fn check_wasm_capabilities(
module: &ParsedWasm,
available_capabilities: &HashSet<String>,
mut logs: Logs,
) -> VmResult<()> {
let required_capabilities = required_capabilities_from_module(module);
if let Some(logs) = logs.open() {
logs.push(format!(
"Required capabilities: {}",
required_capabilities.to_string_limited(20_000)
));
}
if !required_capabilities.is_subset(available_capabilities) {
// We switch to BTreeSet to get a sorted error message
let unavailable: BTreeSet<_> = required_capabilities
Expand All @@ -255,7 +336,20 @@ fn check_wasm_capabilities(
Ok(())
}

fn check_wasm_functions(module: &ParsedWasm) -> VmResult<()> {
fn check_wasm_functions(module: &ParsedWasm, mut logs: Logs) -> VmResult<()> {
if let Some(logs) = logs.open() {
logs.push(format!("Function count: {}", module.function_count));
logs.push(format!(
"Max function parameters: {}",
module.max_func_params
));
logs.push(format!("Max function results: {}", module.max_func_results));
logs.push(format!(
"Total function parameter count: {}",
module.total_func_params
));
}

if module.function_count > MAX_FUNCTIONS {
return Err(VmError::static_validation_err(format!(
"Wasm contract contains more than {MAX_FUNCTIONS} functions"
Expand Down Expand Up @@ -298,6 +392,31 @@ mod tests {
capabilities_from_csv("cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,iterator,staking,stargate")
}

#[test]
fn logs_works() {
let mut logs = Logs::new();

if let Some(logs) = logs.open() {
logs.push(format!("a test"));
}

if let Some(logs) = logs.open() {
logs.push(format!("second test"));
logs.push(format!("third test"));
}

let mut logs_b = logs.clone();
if let Some(logs) = logs_b.open() {
logs.push(format!("added in b"));
}

let mut iter = logs.iter();
assert_eq!(iter.next(), Some(String::from("a test")));
assert_eq!(iter.next(), Some(String::from("second test")));
assert_eq!(iter.next(), Some(String::from("third test")));
assert_eq!(iter.next(), None);
}

#[test]
fn check_wasm_passes_for_latest_contract() {
// this is our reference check, must pass
Expand Down Expand Up @@ -599,7 +718,7 @@ mod tests {
)
.unwrap();
let module = ParsedWasm::parse(&wasm).unwrap();
check_wasm_exports(&module).unwrap();
check_wasm_exports(&module, Off).unwrap();

// this is invalid, as it doesn't any required export
let wasm = wat::parse_str(
Expand All @@ -611,7 +730,7 @@ mod tests {
)
.unwrap();
let module = ParsedWasm::parse(&wasm).unwrap();
match check_wasm_exports(&module) {
match check_wasm_exports(&module, Off) {
Err(VmError::StaticValidationErr { msg, .. }) => {
assert!(msg.starts_with("Wasm contract doesn't have required export: \"allocate\""));
}
Expand All @@ -630,7 +749,7 @@ mod tests {
)
.unwrap();
let module = ParsedWasm::parse(&wasm).unwrap();
match check_wasm_exports(&module) {
match check_wasm_exports(&module, Off) {
Err(VmError::StaticValidationErr { msg, .. }) => {
assert!(
msg.starts_with("Wasm contract doesn't have required export: \"deallocate\"")
Expand Down Expand Up @@ -660,7 +779,7 @@ mod tests {
)"#,
)
.unwrap();
check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS).unwrap();
check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS, Off).unwrap();
}

#[test]
Expand Down Expand Up @@ -771,8 +890,8 @@ mod tests {
)"#,
)
.unwrap();
let err =
check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS).unwrap_err();
let err = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS, Off)
.unwrap_err();
match err {
VmError::StaticValidationErr { msg, .. } => {
assert_eq!(msg, "Import count exceeds limit. Imports: 101. Limit: 100.");
Expand Down Expand Up @@ -809,7 +928,7 @@ mod tests {
"env.debug",
"env.query_chain",
];
let result = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), supported_imports);
let result = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), supported_imports, Off);
match result.unwrap_err() {
VmError::StaticValidationErr { msg, .. } => {
println!("{msg}");
Expand All @@ -825,7 +944,7 @@ mod tests {
#[test]
fn check_wasm_imports_of_old_contract() {
let module = &ParsedWasm::parse(CONTRACT_0_7).unwrap();
let result = check_wasm_imports(module, SUPPORTED_IMPORTS);
let result = check_wasm_imports(module, SUPPORTED_IMPORTS, Off);
match result.unwrap_err() {
VmError::StaticValidationErr { msg, .. } => {
assert!(
Expand All @@ -839,7 +958,7 @@ mod tests {
#[test]
fn check_wasm_imports_wrong_type() {
let wasm = wat::parse_str(r#"(module (import "env" "db_read" (memory 1 1)))"#).unwrap();
let result = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS);
let result = check_wasm_imports(&ParsedWasm::parse(&wasm).unwrap(), SUPPORTED_IMPORTS, Off);
match result.unwrap_err() {
VmError::StaticValidationErr { msg, .. } => {
assert!(
Expand Down Expand Up @@ -874,7 +993,7 @@ mod tests {
]
.into_iter()
.collect();
check_wasm_capabilities(&module, &available).unwrap();
check_wasm_capabilities(&module, &available, Off).unwrap();
}

#[test]
Expand Down Expand Up @@ -902,7 +1021,7 @@ mod tests {
]
.into_iter()
.collect();
match check_wasm_capabilities(&module, &available).unwrap_err() {
match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
VmError::StaticValidationErr { msg, .. } => assert_eq!(
msg,
"Wasm contract requires unavailable capabilities: {\"sun\"}"
Expand All @@ -918,7 +1037,7 @@ mod tests {
]
.into_iter()
.collect();
match check_wasm_capabilities(&module, &available).unwrap_err() {
match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
VmError::StaticValidationErr { msg, .. } => assert_eq!(
msg,
"Wasm contract requires unavailable capabilities: {\"sun\", \"water\"}"
Expand All @@ -928,7 +1047,7 @@ mod tests {

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

// Available set 4
let available = [].into_iter().collect();
match check_wasm_capabilities(&module, &available).unwrap_err() {
match check_wasm_capabilities(&module, &available, Off).unwrap_err() {
VmError::StaticValidationErr { msg, .. } => assert_eq!(
msg,
"Wasm contract requires unavailable capabilities: {\"nutrients\", \"sun\", \"water\"}"
Expand All @@ -960,7 +1079,7 @@ mod tests {
.unwrap();
let module = ParsedWasm::parse(&wasm).unwrap();

match check_wasm_functions(&module).unwrap_err() {
match check_wasm_functions(&module, Off).unwrap_err() {
VmError::StaticValidationErr { msg, .. } => assert_eq!(
msg,
"Wasm contract contains function with more than 100 parameters"
Expand All @@ -979,7 +1098,7 @@ mod tests {
))
.unwrap();
let module = ParsedWasm::parse(&wasm).unwrap();
match check_wasm_functions(&module).unwrap_err() {
match check_wasm_functions(&module, Off).unwrap_err() {
VmError::StaticValidationErr { msg, .. } => assert_eq!(
msg,
"Wasm contract contains function with more than 1 results"
Expand All @@ -998,7 +1117,7 @@ mod tests {
))
.unwrap();
let module = ParsedWasm::parse(&wasm).unwrap();
match check_wasm_functions(&module).unwrap_err() {
match check_wasm_functions(&module, Off).unwrap_err() {
VmError::StaticValidationErr { msg, .. } => {
assert_eq!(msg, "Wasm contract contains more than 20000 functions")
}
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub mod internals {
//! Please don't use any of these types directly, as
//! they might change frequently or be removed in the future.
pub use crate::compatibility::check_wasm;
pub use crate::compatibility::{check_wasm, check_wasm_with_logs, Logs};
pub use crate::instance::instance_from_module;
pub use crate::wasm_backend::{compile, make_compiling_engine, make_runtime_engine};
}

0 comments on commit 541d3ce

Please sign in to comment.