#![allow(unused_imports)]
use super::common::*;
use neo_devpack_solidity::cli::compile_contracts;
use neo_devpack_solidity::runtime::types::StackItem;
use neo_devpack_solidity::runtime::{NeoRuntime, RuntimeConfig};
use num_bigint::BigUint;
use proptest::prelude::*;
use serde_json::{json, Value};
use std::process::Command;
use tempfile::tempdir;
fn compiler_path() -> &'static str {
env!("CARGO_BIN_EXE_neo-solc")
}
fn run_standard_json(input: &Value) -> (Value, std::process::ExitStatus) {
let dir = tempdir().expect("tempdir");
let input_path = dir.path().join("input.json");
let output_path = dir.path().join("out.json");
std::fs::write(
&input_path,
serde_json::to_string_pretty(input).expect("serialise input"),
)
.expect("write input");
let proc_out = Command::new(compiler_path())
.arg("--standard-json")
.arg("--input")
.arg(&input_path)
.arg("--output")
.arg(&output_path)
.output()
.expect("spawn neo-solc");
let body = std::fs::read_to_string(&output_path).unwrap_or_else(|_| {
format!(
"{{\"errors\":[{{\"message\":\"no output written; stderr={}\"}}]}}",
String::from_utf8_lossy(&proc_out.stderr).replace('"', "'")
)
});
let parsed: Value = serde_json::from_str(&body).unwrap_or_else(|e| {
json!({
"errors": [{ "message": format!("parse output failed: {e}; raw={body}") }]
})
});
(parsed, proc_out.status)
}
fn error_summary(output: &Value) -> String {
output
.get("errors")
.and_then(|e| e.as_array())
.map(|arr| {
arr.iter()
.map(|e| {
let typ = e.get("type").and_then(|v| v.as_str()).unwrap_or("?");
let msg = e.get("message").and_then(|v| v.as_str()).unwrap_or("");
format!("[{typ}] {msg}")
})
.collect::<Vec<_>>()
.join(" | ")
})
.unwrap_or_default()
}
fn get_contract<'a>(output: &'a Value, file: &str, contract: &str) -> Option<&'a Value> {
output.get("contracts")?.get(file)?.get(contract)
}
proptest! {
#![proptest_config(ProptestConfig {
// Each case spawns a subprocess.
cases: 4,
..ProptestConfig::default()
})]
#[test]
fn library_using_for_uint256_compile(seed in 0u32..16) {
let _ = seed;
let safemath_src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
}
"#;
let main_src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./SafeMath.sol";
contract Main {
using SafeMath for uint256;
function f(uint256 x, uint256 y) external pure returns (uint256) {
return x.add(y);
}
}
"#;
let input = json!({
"language": "Solidity",
"sources": {
"SafeMath.sol": { "content": safemath_src },
"Main.sol": { "content": main_src },
},
"settings": {}
});
let (output, status) = run_standard_json(&input);
prop_assert!(
status.success(),
"library compile (a) exited non-zero: errors={}",
error_summary(&output)
);
let main = get_contract(&output, "Main.sol", "Main");
prop_assert!(
main.is_some(),
"Main not present in standard-JSON output: errors={}; contracts={}",
error_summary(&output),
output.get("contracts").map(|v| v.to_string()).unwrap_or_default()
);
let _safemath_present = get_contract(&output, "SafeMath.sol", "SafeMath").is_some();
let method_ids = main
.and_then(|m| m.get("evm"))
.and_then(|m| m.get("methodIdentifiers"))
.and_then(|m| m.as_object());
let has_f = method_ids
.map(|m| m.keys().any(|k| k == "f(uint256,uint256)"))
.unwrap_or(false);
prop_assert!(
has_f,
"expected Main to expose f(uint256,uint256) — confirms cross-\
source `using SafeMath for uint256` binding reached the \
dispatcher; methodIdentifiers={:?}, errors={}",
method_ids,
error_summary(&output)
);
}
}
proptest! {
#![proptest_config(ProptestConfig {
// Each case compiles + deploys + invokes; keep modest.
cases: 6,
..ProptestConfig::default()
})]
#[test]
fn library_using_for_uint256_runtime(
x in 0u32..1_000_000u32,
y in 0u32..1_000_000u32,
) {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
}
contract Main {
using SafeMath for uint256;
function f(uint256 x, uint256 y) external pure returns (uint256) {
return x.add(y);
}
}"#;
let arts = compile_contracts(src, false, 2)
.unwrap_or_else(|e| panic!("library_using_for_uint256_runtime compile: {:?}", e));
prop_assert!(!arts.is_empty(), "compile produced no artifacts");
let art = arts
.iter()
.find(|a| a.metadata.name == "Main")
.unwrap_or(&arts[0]);
let mut rt_pin = NeoRuntime::new(RuntimeConfig::default()).expect("rt_pin");
let r_pin = rt_pin
.call_method_with_deploy_args(
&art.bytecode, &art.tokens, &art.manifest,
"f",
&[StackItem::Integer(7), StackItem::Integer(5)],
None,
)
.expect("Main.f(7, 5) host-level");
prop_assert!(
r_pin.success,
"Canonical pin f(7, 5) must succeed; exc={:?}. Failure here \
means library inlining produced uncallable bytecode for the \
cross-source `using SafeMath for uint256; x.add(y)` path.",
r_pin.exception.as_ref().map(|e| &e.message)
);
prop_assert_eq!(
decode_uint_le(&r_pin.return_data),
BigUint::from(12u64),
"Canonical pin: f(7, 5) must return 12; got rd_hex={}. \
SafeMath.add was either not inlined, or was inlined but with \
wrong operand order / argument routing.",
hex::encode(&r_pin.return_data)
);
let mut rt_fz = NeoRuntime::new(RuntimeConfig::default()).expect("rt_fz");
let r_fz = rt_fz
.call_method_with_deploy_args(
&art.bytecode, &art.tokens, &art.manifest,
"f",
&[StackItem::Integer(x as i64), StackItem::Integer(y as i64)],
None,
)
.expect("Main.f(x, y) host-level (fuzzed)");
prop_assert!(
r_fz.success,
"Fuzzed f({}, {}) must succeed; exc={:?}",
x, y, r_fz.exception.as_ref().map(|e| &e.message)
);
let expected = BigUint::from(x as u64) + BigUint::from(y as u64);
prop_assert_eq!(
decode_uint_le(&r_fz.return_data),
expected.clone(),
"Fuzzed f({}, {}) must return {}; got rd_hex={}. Library-add \
diverged from native `+` for these inputs.",
x, y, expected, hex::encode(&r_fz.return_data)
);
}
}
#[test]
fn library_external_function_separate_address() {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
library SafeMath {
function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
}
contract Main {
function f(uint256 x, uint256 y) external pure returns (uint256) {
return SafeMath.safeAdd(x, y);
}
}"#;
let result = compile_contracts(src, false, 2);
match result {
Err(e) => {
let msg = format!("{:?}", e);
eprintln!(
"library_external_function_separate_address: compiler REJECTED \
`public pure` library function. Diagnostic: {}",
msg
);
}
Ok(arts) => {
assert!(
!arts.is_empty(),
"library_external_function_separate_address: compile succeeded \
but produced zero artifacts — suspicious; means SafeMath was \
erased without producing the linked library contract."
);
let main = arts
.iter()
.find(|a| a.metadata.name == "Main")
.unwrap_or(&arts[0]);
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("rt");
let r = rt
.call_method_with_deploy_args(
&main.bytecode,
&main.tokens,
&main.manifest,
"f",
&[StackItem::Integer(7), StackItem::Integer(5)],
None,
)
.expect("Main.f host-level (public-library path)");
if r.success {
let v = decode_uint_le(&r.return_data);
if v == BigUint::from(12u64) {
eprintln!(
"library_external_function_separate_address: compiler \
ACCEPTED `public pure` library function and returned \
12 = INLINED PATH (treating public as internal for \
pure functions with no external dispatch impact)."
);
} else {
panic!(
"library_external_function_separate_address: compiler \
accepted `public pure` library and Main.f(7, 5) \
succeeded but returned {} (expected 12). The inline \
either evaluated wrong operands or the linked-call \
path returned bogus data.",
v
);
}
} else {
let exc_msg = r
.exception
.as_ref()
.map(|e| e.message.clone())
.unwrap_or_else(|| "no exception".to_string());
eprintln!(
"library_external_function_separate_address: compiler \
ACCEPTED `public pure` library function but Main.f(7, 5) \
faulted at runtime — likely the compiler emitted a CALLT \
/ method-token reference to a library that wasn't \
deployed alongside Main (deployment dance required). \
Exception: {}",
exc_msg
);
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 6,
..ProptestConfig::default()
})]
#[test]
fn library_three_function_chain(
x in 0u16..=10_000u16,
y in 0u16..=10_000u16,
z in 1u16..=100u16,
w_factor in 0u16..=100u16,
) {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
library L {
function add(uint256 a, uint256 b) internal pure returns (uint256) { return a + b; }
function sub(uint256 a, uint256 b) internal pure returns (uint256) { return a - b; }
function mul(uint256 a, uint256 b) internal pure returns (uint256) { return a * b; }
}
contract Main {
using L for uint256;
function chain(uint256 x, uint256 y, uint256 z, uint256 w) external pure returns (uint256) {
return x.add(y).mul(z).sub(w);
}
}"#;
let arts = compile_contracts(src, false, 2)
.unwrap_or_else(|e| panic!("library_three_function_chain compile: {:?}", e));
prop_assert!(!arts.is_empty(), "compile produced no artifacts");
let art = arts
.iter()
.find(|a| a.metadata.name == "Main")
.unwrap_or(&arts[0]);
let xy = (x as u64) + (y as u64);
let xy_z = xy * (z as u64);
let w = if xy_z == 0 { 0u64 } else { (w_factor as u64).min(xy_z) };
let expected = xy_z - w;
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("rt");
let r = rt
.call_method_with_deploy_args(
&art.bytecode, &art.tokens, &art.manifest,
"chain",
&[
StackItem::Integer(x as i64),
StackItem::Integer(y as i64),
StackItem::Integer(z as i64),
StackItem::Integer(w as i64),
],
None,
)
.expect("Main.chain host-level");
prop_assert!(
r.success,
"chain({}, {}, {}, {}) must succeed; exc={:?}. Failure means \
one of add/mul/sub failed to inline OR the receiver re-bind \
on the result of a previous library call dropped — i.e. \
`x.add(y)` returned a uint256 but the parser/lowering did \
not re-attach `.mul(...)` against the new uint256 receiver.",
x, y, z, w, r.exception.as_ref().map(|e| &e.message)
);
prop_assert_eq!(
decode_uint_le(&r.return_data),
BigUint::from(expected),
"chain({}, {}, {}, {}) must return ((x+y)*z)-w = {}; got \
rd_hex={}. Either operator order diverged from Solidity's \
left-to-right (e.g. mul applied before add), or one library \
function inlined wrong operands.",
x, y, z, w, expected, hex::encode(&r.return_data)
);
}
}