#![allow(unused_imports)]
use super::common::*;
use neo_devpack_solidity::cli::compile_contracts;
use neo_devpack_solidity::runtime::{NeoRuntime, RuntimeConfig};
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
const SCAN_ROOTS: &[&str] = &["examples", "devpack/contracts", "devpack/examples"];
fn collect_sol_files_recursive(root: &Path) -> Vec<PathBuf> {
let mut out: Vec<PathBuf> = Vec::new();
if !root.is_dir() {
return out;
}
let mut stack: Vec<PathBuf> = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let entries = match std::fs::read_dir(&dir) {
Ok(e) => e,
Err(_) => continue,
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if path.is_file() && path.extension().and_then(|e| e.to_str()) == Some("sol") {
out.push(path);
}
}
}
out.sort();
out
}
fn has_import(src: &str) -> bool {
src.lines().any(|line| {
let trimmed = line.trim_start();
trimmed.starts_with("import ")
|| trimmed.starts_with("import\"")
|| trimmed.starts_with("import{")
|| trimmed.starts_with("import (")
})
}
fn is_intentional_failure(path: &Path) -> bool {
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
name.ends_with("Error.sol") || name.starts_with("EvmCompat")
}
fn pick_zero_arg_safe_method(manifest: &serde_json::Value) -> Option<String> {
let methods = manifest.get("abi")?.get("methods")?.as_array()?;
for m in methods {
let name = m.get("name").and_then(|v| v.as_str()).unwrap_or("");
if name.is_empty() || name.starts_with('_') {
continue;
}
let safe = m.get("safe").and_then(|v| v.as_bool()).unwrap_or(false);
let params = m
.get("parameters")
.and_then(|v| v.as_array())
.map(|a| a.len())
.unwrap_or(0);
if safe && params == 0 {
return Some(name.to_string());
}
}
None
}
fn is_acceptable_init_failure(msg: &str) -> bool {
let m = msg.to_lowercase();
m.contains("address account is not bound")
|| m.contains("pickitem: unsupported target null")
|| m.contains("gas exhausted")
|| m.contains("out of gas")
|| m.contains("throw")
|| m.contains("abort")
|| m.contains("panic: 0x")
|| m.contains("panic(0x")
|| m.contains("uninitialised")
|| m.contains("uninitialized")
|| m.contains("not initialized")
|| m.contains("not initialised")
|| m.contains("not authorized")
|| m.contains("only owner")
|| m.contains("forbidden")
|| m.contains("invalid witness")
|| m.contains("witness")
|| m.contains("not authorised")
|| m.contains("index out of bounds")
|| m.contains("underflow")
|| m.contains("overflow")
}
enum CompileOutcome {
Compiled {
path: PathBuf,
artifacts: Vec<neo_devpack_solidity::cli::CompilationArtifacts>,
},
SkippedImport,
SkippedNegativeTest,
UnexpectedFailure {
path: PathBuf,
error: String,
},
}
fn compile_one(path: &Path) -> CompileOutcome {
let src = match read_to_string(path) {
Ok(s) => s,
Err(e) => {
return CompileOutcome::UnexpectedFailure {
path: path.to_path_buf(),
error: format!("read_to_string: {}", e),
};
}
};
if has_import(&src) {
return CompileOutcome::SkippedImport;
}
let is_negative = is_intentional_failure(path);
match compile_contracts(&src, false, 2) {
Ok(artifacts) => {
if is_negative {
CompileOutcome::SkippedNegativeTest
} else if artifacts.is_empty() {
CompileOutcome::UnexpectedFailure {
path: path.to_path_buf(),
error: "compiled OK but produced zero artifacts".into(),
}
} else {
CompileOutcome::Compiled {
path: path.to_path_buf(),
artifacts,
}
}
}
Err(e) => {
if is_negative {
CompileOutcome::SkippedNegativeTest
} else {
let msg = format!("{:?}", e);
let truncated: String = msg.chars().take(400).collect();
CompileOutcome::UnexpectedFailure {
path: path.to_path_buf(),
error: truncated,
}
}
}
}
}
#[test]
fn examples_compile_smoke() {
let workspace_root = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("."));
let mut total_files = 0usize;
let mut compiled = 0usize;
let mut skipped_import = 0usize;
let mut skipped_negative = 0usize;
let mut failures: Vec<(PathBuf, String)> = Vec::new();
for root in SCAN_ROOTS {
let dir = workspace_root.join(root);
if !dir.is_dir() {
eprintln!(
"[examples_compile_smoke] root {} not a directory; skipping",
dir.display()
);
continue;
}
for path in collect_sol_files_recursive(&dir) {
total_files += 1;
match compile_one(&path) {
CompileOutcome::Compiled { .. } => compiled += 1,
CompileOutcome::SkippedImport => skipped_import += 1,
CompileOutcome::SkippedNegativeTest => skipped_negative += 1,
CompileOutcome::UnexpectedFailure { path, error } => {
failures.push((path, error));
}
}
}
}
eprintln!(
"examples_compile_smoke: {} contracts compiled, {} failed \
(surveyed {}, skipped-import {}, skipped-negative-test {})",
compiled,
failures.len(),
total_files,
skipped_import,
skipped_negative,
);
if !failures.is_empty() {
let summary = failures
.iter()
.map(|(p, e)| format!(" - {}: {}", p.display(), e))
.collect::<Vec<_>>()
.join("\n");
panic!(
"examples_compile_smoke regression: {} shipped contract(s) FAILED to compile.\n\
These are real Solidity files under examples/ or devpack/* that previously\n\
worked — file as a regression and bisect. List of failures:\n{}",
failures.len(),
summary
);
}
assert!(
compiled > 0,
"examples_compile_smoke surveyed {} files but compiled 0 — \
scan-root configuration is wrong, or every file has an import.",
total_files,
);
}
#[test]
fn examples_call_smoke() {
let workspace_root = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("."));
let mut called = 0usize;
let mut faulted: Vec<(PathBuf, String, String)> = Vec::new();
let mut compiled_no_safe_method = 0usize;
let mut total_compiled = 0usize;
for root in SCAN_ROOTS {
let dir = workspace_root.join(root);
if !dir.is_dir() {
continue;
}
for path in collect_sol_files_recursive(&dir) {
let outcome = compile_one(&path);
let (path, artifacts) = match outcome {
CompileOutcome::Compiled { path, artifacts } => (path, artifacts),
_ => continue,
};
total_compiled += 1;
let art = &artifacts[0];
let method = match pick_zero_arg_safe_method(&art.manifest) {
Some(m) => m,
None => {
compiled_no_safe_method += 1;
continue;
}
};
let mut rt = match NeoRuntime::new(RuntimeConfig::default()) {
Ok(r) => r,
Err(e) => {
faulted.push((
path.clone(),
method.clone(),
format!("NeoRuntime::new failed: {:?}", e),
));
continue;
}
};
let r = match rt.call_method(&art.bytecode, &art.tokens, &art.manifest, &method, &[]) {
Ok(r) => r,
Err(e) => {
faulted.push((
path.clone(),
method.clone(),
format!("host-level call_method err: {:?}", e),
));
continue;
}
};
called += 1;
if r.success {
continue;
}
let exc_msg = r
.exception
.as_ref()
.map(|e| e.message.clone())
.unwrap_or_else(|| "no exception populated".to_string());
if is_acceptable_init_failure(&exc_msg) {
continue;
}
faulted.push((path.clone(), method.clone(), exc_msg));
}
}
eprintln!(
"examples_call_smoke: {} contracts called pure/view, {} faulted \
(compiled {}, compiled-without-safe-method {})",
called,
faulted.len(),
total_compiled,
compiled_no_safe_method,
);
if !faulted.is_empty() {
let summary = faulted
.iter()
.map(|(p, m, e)| format!(" - {}::{} -> {}", p.display(), m, e))
.collect::<Vec<_>>()
.join("\n");
panic!(
"examples_call_smoke regression: {} shipped contract(s) FAULTED at runtime\n\
when invoking a public zero-arg pure/view method on a fresh NeoRuntime.\n\
These are not graceful init failures (those are whitelisted) — they are\n\
unexpected host-level or contract-level faults. File as a regression.\n{}",
faulted.len(),
summary,
);
}
assert!(
called > 0,
"examples_call_smoke ran zero pure/view calls — either compile_smoke\n\
is broken or no shipped contract exposes a zero-arg safe method.\n\
total_compiled={}, compiled_without_safe_method={}",
total_compiled,
compiled_no_safe_method,
);
}