use std::path::PathBuf;
use std::process::Command;
use tempfile::tempdir;
fn compiler_path() -> &'static str {
env!("CARGO_BIN_EXE_neo-solc")
}
fn compile_source(source: &str, opt: &str) -> (std::process::Output, tempfile::TempDir, PathBuf) {
let dir = tempdir().expect("tempdir");
let input = dir.path().join("input.sol");
std::fs::write(&input, source).expect("write source");
let prefix = dir.path().join("out");
let output = Command::new(compiler_path())
.arg(&input)
.arg(opt)
.arg("-o")
.arg(&prefix)
.output()
.expect("run compiler");
(output, dir, prefix)
}
fn assert_not_aborted(output: &std::process::Output, what: &str) {
assert!(
output.status.code().is_some(),
"{what}: compiler was killed by a signal (stack overflow / OOM abort): {:?}\nstderr: {}",
output.status,
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn huge_shift_literal_compiles_cleanly_at_o2() {
let sources = [
"contract C { function f() public pure returns (uint) { return 1 << 18000000000000000000; } }",
"contract C { function f() public pure returns (uint) { return 1 << 0xffffffffffffffff; } }",
"contract C { function f() public pure returns (uint) { return (2**1024) << 5000000000; } }",
];
for source in sources {
let (output, _dir, _) = compile_source(source, "-O2");
assert_not_aborted(&output, "huge shift literal");
assert!(
output.status.success(),
"huge shift literal must compile (runtime op), got: {}\n{}",
output.status,
String::from_utf8_lossy(&output.stderr)
);
}
}
#[test]
fn sub_abort_shift_no_longer_balloons_the_nef() {
let source =
"contract C { function f() public pure returns (uint) { return 1 << 200000000; } }";
let (output, _dir, prefix) = compile_source(source, "-O2");
assert!(
output.status.success(),
"sub-abort shift must compile: {}",
String::from_utf8_lossy(&output.stderr)
);
let nef = prefix.with_extension("nef");
let size = std::fs::metadata(&nef)
.unwrap_or_else(|e| panic!("missing {}: {e}", nef.display()))
.len();
assert!(
size < 1024 * 1024,
"oversized shift must not be folded into the bytecode (nef is {size} bytes)"
);
}
#[test]
fn chained_huge_literal_multiplies_compile_cleanly_at_o2() {
let source = "contract C { function f() public pure returns (uint) { \
return (1 << 4000) * (1 << 4000) * (1 << 4000) * (1 << 4000); } }";
let (output, _dir, prefix) = compile_source(source, "-O2");
assert_not_aborted(&output, "chained huge multiplies");
assert!(
output.status.success(),
"chained huge multiplies must compile: {}",
String::from_utf8_lossy(&output.stderr)
);
let nef = prefix.with_extension("nef");
let size = std::fs::metadata(&nef).expect("nef metadata").len();
assert!(
size < 1024 * 1024,
"huge-literal product must not be folded into the bytecode (nef is {size} bytes)"
);
}
#[test]
fn legal_uint256_constants_still_compile_at_o2() {
let source = "contract C { function f() public pure returns (uint) { \
return (1 << 255) + (3 * 5) + (115792089237316195423570985008687907853269984665640564039457584007913129639935 & 0xff); } }";
let (output, _dir, _) = compile_source(source, "-O2");
assert!(
output.status.success(),
"legal uint256 constants must keep compiling at -O2: {}",
String::from_utf8_lossy(&output.stderr)
);
}
fn deeply_nested_source(terms: usize) -> String {
let mut source =
String::from("contract C { function f() public pure returns (uint) { return 1 ");
for _ in 0..terms {
source.push_str("+1");
}
source.push_str("; } }");
source
}
#[test]
fn deeply_nested_expression_compiles_without_stack_overflow() {
let (output, _dir, _) = compile_source(&deeply_nested_source(3000), "-O2");
assert_not_aborted(&output, "3000-term nested expression");
assert!(
output.status.success(),
"3000-term nested expression must compile: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn deeply_nested_expression_standard_json_no_abort() {
let dir = tempdir().expect("tempdir");
let input_path = dir.path().join("input.json");
let output_path = dir.path().join("out.json");
let input = serde_json::json!({
"language": "Solidity",
"sources": {
"C.sol": { "content": deeply_nested_source(3000) }
},
"settings": {}
});
std::fs::write(
&input_path,
serde_json::to_string(&input).expect("serialize"),
)
.expect("write input");
let output = Command::new(compiler_path())
.arg("--standard-json")
.arg("--input")
.arg(&input_path)
.arg("--output")
.arg(&output_path)
.output()
.expect("run compiler");
assert_not_aborted(&output, "standard-json 3000-term nested expression");
assert!(
output.status.success(),
"standard-json deep nesting must produce a clean result: {}",
String::from_utf8_lossy(&output.stderr)
);
let json: serde_json::Value = serde_json::from_str(
&std::fs::read_to_string(&output_path).expect("read standard-json output"),
)
.expect("parse standard-json output");
assert!(
json["contracts"].is_object(),
"expected compiled contracts in standard-json output: {json}"
);
}
#[test]
fn parse_source_library_api_survives_deep_nesting() {
let source = deeply_nested_source(3000);
let contracts = neo_devpack_solidity::frontend::parse_source(&source)
.expect("deeply nested source must parse via the guarded helper");
assert_eq!(contracts.len(), 1);
assert_eq!(contracts[0].name, "C");
}