#![allow(unused_imports)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::single_match)]
#![allow(clippy::partialeq_to_none)]
use super::common::*;
use neo_devpack_solidity::cli::{
compile_contracts, compile_contracts_with_options, CompileOptions,
};
use neo_devpack_solidity::runtime::{NeoRuntime, RuntimeConfig};
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn deterministic_compilation_full_bytecode_and_manifest(
var_name in identifier_strategy()
) {
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TestContract {{
uint256 public {} = 42;
}}"#,
var_name
);
let result1 = compile_contracts(&source, false, 2);
let result2 = compile_contracts(&source, false, 2);
prop_assert!(result1.is_ok(), "first compile failed: {:?}", result1.err());
prop_assert!(result2.is_ok(), "second compile failed: {:?}", result2.err());
let artifacts1 = result1.unwrap();
let artifacts2 = result2.unwrap();
prop_assert_eq!(artifacts1.len(), artifacts2.len());
for (a1, a2) in artifacts1.iter().zip(artifacts2.iter()) {
prop_assert_eq!(&a1.bytecode, &a2.bytecode,
"bytecode differed between deterministic runs");
prop_assert_eq!(&a1.manifest, &a2.manifest,
"manifest differed between deterministic runs");
}
}
#[test]
fn nef_checksum_validates(
var_name in identifier_strategy()
) {
use neo_devpack_solidity::neo::build_nef_with_tokens;
use sha2::{Digest, Sha256};
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TestContract {{
uint256 public {} = 1;
}}"#,
var_name
);
let artifacts = compile_contracts(&source, false, 2).expect("compile");
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let nef = build_nef_with_tokens(
&artifact.bytecode,
"neo-devpack-solidity-fuzz",
"",
&artifact.tokens,
).expect("NEF should build");
prop_assert!(nef.len() > 4, "NEF must contain more than the trailer");
let prefix = &nef[..nef.len() - 4];
let stored_trailer = &nef[nef.len() - 4..];
let first = Sha256::digest(prefix);
let second = Sha256::digest(first);
prop_assert_eq!(stored_trailer, &second[..4],
"NEF trailer does not match sha256(sha256(prefix))[..4]");
}
#[test]
fn manifest_json_roundtrip(
var_name in identifier_strategy()
) {
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract TestContract {{
uint256 public {} = 7;
}}"#,
var_name
);
let artifacts = compile_contracts(&source, false, 2).expect("compile");
prop_assert!(!artifacts.is_empty());
let manifest = &artifacts[0].manifest;
let as_string = serde_json::to_string(manifest)
.expect("manifest must serialize to JSON");
let reparsed: serde_json::Value = serde_json::from_str(&as_string)
.expect("manifest JSON must reparse");
prop_assert_eq!(&reparsed, manifest, "JSON round-trip was not lossless");
for key in ["name", "abi", "permissions", "supportedstandards"] {
prop_assert!(
reparsed.get(key).is_some(),
"manifest missing required top-level key: {}",
key
);
}
}
#[test]
fn enum_storage_roundtrip(
getter_name in identifier_strategy()
) {
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract EnumContract {{
enum Mode {{ Idle, Active, Paused, Suspended, Finalised }}
Mode public {} = Mode.Idle;
}}"#,
getter_name
);
let artifacts = compile_contracts(&source, false, 2).expect("enum contract compile");
prop_assert_eq!(artifacts.len(), 1);
let manifest = &artifacts[0].manifest;
let methods = manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let getter = methods.iter().find(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(getter_name.as_str())
});
prop_assert!(
getter.is_some(),
"auto-generated getter '{}' missing from manifest methods",
getter_name
);
let returntype = getter.unwrap()
.get("returntype")
.and_then(serde_json::Value::as_str);
prop_assert_eq!(
returntype,
Some("Integer"),
"enum getter returntype should be Integer; got {:?}",
returntype
);
}
#[test]
fn precompile_identity_passthrough(
fn_name in identifier_strategy()
) {
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
library InlinePrecompiles {{
function identity(bytes memory data) internal pure returns (bytes memory) {{
return data;
}}
}}
contract IdentityShowcase {{
function {}(bytes memory data) public pure returns (bytes memory) {{
return InlinePrecompiles.identity(data);
}}
}}"#,
fn_name
);
let artifacts = compile_contracts(&source, false, 2).expect("identity contract compile");
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
prop_assert!(!artifacts[0].bytecode.is_empty(), "bytecode should be non-empty");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let declared = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(fn_name.as_str())
});
prop_assert!(declared,
"precompile wrapper method '{}' not declared in manifest", fn_name);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn sha256_solidity_compiles_and_hash_reference_matches(
fn_name in identifier_strategy(),
payload in prop::collection::vec(any::<u8>(), 0..256)
) {
use sha2::{Digest, Sha256};
let reference = Sha256::digest(&payload);
prop_assert_eq!(reference.len(), 32, "sha256 digest must be 32 bytes");
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract Sha256Showcase {{
function {}(bytes memory data) public pure returns (bytes32) {{
return sha256(data);
}}
}}"#,
fn_name
);
let artifacts = compile_contracts(&source, false, 2).expect("sha256 contract compile");
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
prop_assert!(!artifacts[0].bytecode.is_empty(), "bytecode should be non-empty");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let declared = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(fn_name.as_str())
});
prop_assert!(declared, "sha256 wrapper method '{}' missing from manifest", fn_name);
use neo_devpack_solidity::runtime::types::StackItem;
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let args = [StackItem::byte_array(payload.clone())];
let result = runtime
.call_method(&artifacts[0].bytecode, &artifacts[0].tokens, &artifacts[0].manifest,
fn_name.as_str(), &args)
.expect("sha256 wrapper call_method should not error at the Rust boundary");
prop_assert!(result.success,
"sha256 wrapper execution should succeed; got exception {:?}", result.exception);
prop_assert_eq!(&result.return_data, &reference.to_vec(),
"sha256(payload) must equal sha2::Sha256::digest(payload); \
payload_len={} expected={} got={}",
payload.len(), hex::encode(&reference), hex::encode(&result.return_data));
}
#[test]
fn ripemd160_compile_and_reference_length(
fn_name in identifier_strategy(),
payload in prop::collection::vec(any::<u8>(), 0..256)
) {
use ripemd::{Digest, Ripemd160};
let reference = Ripemd160::digest(&payload);
prop_assert_eq!(reference.len(), 20, "ripemd160 digest must be 20 bytes");
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract Ripemd160Showcase {{
function {}(bytes memory data) public pure returns (bytes20) {{
return ripemd160(data);
}}
}}"#,
fn_name
);
let artifacts = compile_contracts(&source, false, 2).expect("ripemd160 contract compile");
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
prop_assert!(!artifacts[0].bytecode.is_empty(), "bytecode should be non-empty");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let declared = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(fn_name.as_str())
});
prop_assert!(declared, "ripemd160 wrapper method '{}' missing from manifest", fn_name);
use neo_devpack_solidity::runtime::types::StackItem;
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let args = [StackItem::byte_array(payload.clone())];
let result = runtime
.call_method(&artifacts[0].bytecode, &artifacts[0].tokens, &artifacts[0].manifest,
fn_name.as_str(), &args)
.expect("ripemd160 wrapper call_method should not error at the Rust boundary");
prop_assert!(result.success,
"ripemd160 wrapper execution should succeed; got exception {:?}", result.exception);
prop_assert_eq!(&result.return_data, &reference.to_vec(),
"ripemd160(payload) must equal ripemd::Ripemd160::digest(payload); \
payload_len={} expected={} got={}",
payload.len(), hex::encode(&reference), hex::encode(&result.return_data));
}
#[test]
fn modexp_matches_num_bigint(
fn_name in identifier_strategy(),
base in 0u64..=u32::MAX as u64,
exp in any::<u64>(),
modulus in 1u64..=(u32::MAX as u64)
) {
use num_bigint::BigUint;
use num_traits::Zero;
let base_bi = BigUint::from(base);
let exp_bi = BigUint::from(exp);
let mod_bi = BigUint::from(modulus);
let result = base_bi.modpow(&exp_bi, &mod_bi);
prop_assert!(result < mod_bi, "modpow result must be strictly less than modulus");
if mod_bi == BigUint::from(1u8) {
prop_assert!(result.is_zero(), "modpow mod 1 must be 0");
}
if exp_bi.is_zero() && mod_bi > BigUint::from(1u8) {
prop_assert_eq!(&result, &BigUint::from(1u8), "x^0 mod m (m>1) == 1");
}
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ModExpShowcase {{
function {}(uint256 base, uint256 exp, uint256 m) public pure returns (uint256 result) {{
require(m != 0, "modulus is zero");
if (m == 1) return 0;
if (exp == 0) return 1;
result = 1;
base = base % m;
while (exp > 0) {{
if (exp % 2 == 1) {{
result = mulmod(result, base, m);
}}
exp = exp / 2;
base = mulmod(base, base, m);
}}
}}
}}"#,
fn_name
);
let artifacts = compile_contracts(&source, false, 2).expect("modexp contract compile");
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
prop_assert!(!artifacts[0].bytecode.is_empty(), "bytecode should be non-empty");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let declared = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(fn_name.as_str())
});
prop_assert!(declared, "modExp wrapper method '{}' missing from manifest", fn_name);
if base < (1u64 << 32) && (base % modulus) < (1u64 << 32) {
use neo_devpack_solidity::runtime::types::StackItem;
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let args = [
StackItem::UnsignedInteger(base),
StackItem::UnsignedInteger(exp),
StackItem::UnsignedInteger(modulus),
];
let rt_result = runtime
.call_method(&artifacts[0].bytecode, &artifacts[0].tokens, &artifacts[0].manifest,
fn_name.as_str(), &args)
.expect("modExp wrapper call_method should not error at the Rust boundary");
prop_assert!(rt_result.success,
"modExp wrapper execution should succeed; got exception {:?}", rt_result.exception);
let observed = decode_uint_le(&rt_result.return_data);
prop_assert_eq!(&observed, &result,
"modExp({}, {}, {}) must equal num_bigint::BigUint::modpow; \
expected={} got={} return_data={:?}",
base, exp, modulus, result, observed, rt_result.return_data);
}
}
}
#[test]
fn storage_iterator_lex_order() {
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("Failed to create runtime");
let account = "0x1234567890123456789012345678901234567890";
let prefix: &[u8] = b"pfx/";
let entries: Vec<(Vec<u8>, Vec<u8>)> = vec![
(b"pfx/charlie".to_vec(), b"c".to_vec()),
(b"pfx/alpha".to_vec(), b"a".to_vec()),
(b"pfx/bravo".to_vec(), b"b".to_vec()),
(b"other/x".to_vec(), b"x".to_vec()),
(b"pfy/z".to_vec(), b"z".to_vec()),
];
for (k, v) in &entries {
runtime
.set_storage(account, k, v)
.expect("Failed to set storage");
}
let found = runtime
.storage_find(account, prefix)
.expect("storage_find must succeed");
assert_eq!(
found.len(),
3,
"storage_find returned wrong number of matches: {:?}",
found
);
for (k, _) in &found {
assert!(
k.starts_with(prefix),
"storage_find returned key {:?} without prefix {:?}",
k,
prefix
);
}
let keys: Vec<&[u8]> = found.iter().map(|(k, _)| k.as_slice()).collect();
let mut expected_keys = keys.clone();
expected_keys.sort();
assert_eq!(
keys, expected_keys,
"storage_find results must be byte-lex ordered by key"
);
for (k, v) in &found {
let retrieved = runtime
.get_storage(account, k)
.expect("get_storage must succeed");
assert_eq!(retrieved.as_ref(), Some(v));
}
let all = runtime
.storage_find(account, b"")
.expect("storage_find with empty prefix must succeed");
assert_eq!(
all.len(),
entries.len(),
"empty-prefix storage_find must return all entries"
);
}
#[test]
fn nef_round_trip_to_bytes_and_back() {
use neo_devpack_solidity::neo::{build_nef_with_tokens, parse_nef, MethodToken};
let script: Vec<u8> = vec![0x10, 0x11, 0x40]; let compiler = "neo-devpack-solidity-fuzz-roundtrip";
let source = "https://example.test/round-trip";
let tokens = vec![
MethodToken::new([0u8; 20], "transfer", 3, true, 0x0F),
MethodToken::new([0x11u8; 20], "symbol", 0, true, 0x01),
];
let built = build_nef_with_tokens(&script, compiler, source, &tokens)
.expect("build_nef_with_tokens should succeed");
assert!(built.starts_with(b"NEF3"), "NEF must start with magic NEF3");
assert!(built.len() > 4, "NEF must be larger than just the trailer");
let parsed = parse_nef(&built).expect("parse_nef must succeed on a freshly built NEF");
assert_eq!(parsed.compiler, compiler, "compiler field round-trip");
assert_eq!(parsed.source, source, "source field round-trip");
assert_eq!(parsed.script, script, "script payload round-trip");
assert_eq!(parsed.tokens.len(), tokens.len(), "token count round-trip");
for (orig, out) in tokens.iter().zip(parsed.tokens.iter()) {
assert_eq!(orig.hash, out.hash, "token hash round-trip");
assert_eq!(orig.method, out.method, "token method round-trip");
assert_eq!(
orig.parameters_count, out.parameters_count,
"token parameters_count round-trip"
);
assert_eq!(
orig.has_return_value, out.has_return_value,
"token has_return_value round-trip"
);
assert_eq!(
orig.call_flags, out.call_flags,
"token call_flags round-trip"
);
}
let rebuilt = build_nef_with_tokens(
&parsed.script,
&parsed.compiler,
&parsed.source,
&parsed.tokens,
)
.expect("rebuild after parse must succeed");
assert_eq!(
rebuilt, built,
"NEF bytes must be byte-identical after parse→build round-trip"
);
let mut corrupted = built.clone();
let last = corrupted.len() - 1;
corrupted[last] ^= 0xFF;
assert!(
parse_nef(&corrupted).is_err(),
"parse_nef must reject a corrupted checksum"
);
let mut bad_magic = built.clone();
bad_magic[0] = b'X';
assert!(
parse_nef(&bad_magic).is_err(),
"parse_nef must reject a non-NEF3 magic"
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn inheritance_chain_resolves_virtual_override(
base_name in identifier_strategy(),
derived_name in identifier_strategy(),
depth in 1usize..5
) {
prop_assume!(base_name != derived_name);
prop_assume!(!base_name.starts_with("Mid_"));
prop_assume!(!derived_name.starts_with("Mid_"));
let mut contracts = String::new();
contracts.push_str(&format!(
r#"contract {base} {{
function foo() public virtual returns (uint256) {{ return 1; }}
}}
"#,
base = base_name
));
let mut prev = base_name.clone();
for i in 0..depth {
let name = format!("Mid_{}", i);
contracts.push_str(&format!(
r#"contract {name} is {prev} {{
function foo() public virtual override returns (uint256) {{
return super.foo() + {i};
}}
}}
"#,
name = name,
prev = prev,
i = i + 1
));
prev = name;
}
contracts.push_str(&format!(
r#"contract {derived} is {prev} {{
function foo() public override returns (uint256) {{
return super.foo() + 100;
}}
}}
"#,
derived = derived_name,
prev = prev
));
let source = format!(
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.19;\n{}",
contracts
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("inheritance chain compile failed (depth={}): {:?}", depth, e));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
let derived_artifact = artifacts.iter().find(|a| {
a.manifest.get("name").and_then(serde_json::Value::as_str) == Some(derived_name.as_str())
}).unwrap_or(&artifacts[artifacts.len() - 1]);
let methods = derived_artifact.manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let foo_methods: Vec<_> = methods.iter().filter(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("foo")
}).collect();
prop_assert!(!foo_methods.is_empty(),
"expected a `foo` method in Derived manifest, methods={:?}",
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
prop_assert_eq!(foo_methods.len(), 1,
"expected exactly one canonical `foo`; got {}", foo_methods.len());
let returntype = foo_methods[0]
.get("returntype")
.and_then(serde_json::Value::as_str);
prop_assert_eq!(returntype, Some("Integer"),
"foo returntype should be Integer, got {:?}", returntype);
}
#[test]
fn interface_and_abstract_method_resolution(
f1_param_count in 0u32..=3,
) {
let params: String = (0..f1_param_count)
.map(|i| format!("uint256 p{}", i))
.collect::<Vec<_>>()
.join(", ");
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface I {{
function f1({params}) external view returns (uint256);
}}
abstract contract A is I {{
function f2() public virtual;
}}
contract C is A {{
function f1({params}) external pure override returns (uint256) {{ return 1; }}
function f2() public override {{ }}
}}"#,
params = params
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("interface+abstract compile failed (params={}): {:?}", f1_param_count, e));
let c_artifact = artifacts.iter().find(|a| {
a.manifest.get("name").and_then(serde_json::Value::as_str) == Some("C")
}).unwrap_or(&artifacts[artifacts.len() - 1]);
let methods = c_artifact.manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let has_f1 = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("f1")
});
let has_f2 = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("f2")
});
prop_assert!(has_f1, "expected f1 in manifest; methods={:?}",
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
prop_assert!(has_f2, "expected f2 in manifest; methods={:?}",
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
let f1 = methods.iter().find(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("f1")
}).unwrap();
let f1_params = f1["parameters"].as_array().expect("f1.parameters array");
prop_assert_eq!(f1_params.len(), f1_param_count as usize,
"f1 parameter count mismatch: manifest={} expected={}",
f1_params.len(), f1_param_count);
}
#[test]
fn nested_mapping_plus_dynamic_array_compile(
outer_is_uint in any::<bool>(),
inner_is_uint in any::<bool>(),
setter_name in identifier_strategy(),
getter_name in identifier_strategy()
) {
prop_assume!(setter_name != getter_name);
prop_assume!(setter_name != "m" && getter_name != "m");
let outer_ty = if outer_is_uint { "uint256" } else { "address" };
let inner_ty = if inner_is_uint { "uint256" } else { "bytes32" };
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract NestedMappingFuzz {{
mapping({outer_ty} => mapping(address => {inner_ty}[])) public m;
function {setter}({outer_ty} k1, address k2, {inner_ty}[] memory vals) public {{
m[k1][k2] = vals;
}}
function {getter}({outer_ty} k1, address k2, uint256 idx) public view returns ({inner_ty}) {{
return m[k1][k2][idx];
}}
}}"#,
outer_ty = outer_ty,
inner_ty = inner_ty,
setter = setter_name,
getter = getter_name
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!(
"nested mapping compile failed (outer={}, inner={}): {:?}",
outer_ty, inner_ty, e
));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let has_setter = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(setter_name.as_str())
});
let has_getter = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(getter_name.as_str())
});
prop_assert!(has_setter, "setter '{}' missing from manifest", setter_name);
prop_assert!(has_getter, "getter '{}' missing from manifest", getter_name);
let setter_method = methods.iter().find(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(setter_name.as_str())
}).unwrap();
let params = setter_method["parameters"].as_array().expect("setter.parameters array");
prop_assert_eq!(params.len(), 3,
"setter parameter count mismatch: got {}", params.len());
}
#[test]
fn custom_errors_with_parameters_compile(
err_count in 1usize..=5,
fn_name in identifier_strategy(),
err_names in prop::collection::vec(identifier_strategy(), 5),
param_counts in prop::collection::vec(0u32..=3, 5)
) {
let mut names: Vec<String> = err_names.into_iter().take(err_count).collect();
names.push(fn_name.clone());
let mut seen = std::collections::HashSet::new();
names.retain(|n| seen.insert(n.clone()));
prop_assume!(names.len() == err_count + 1);
let err_names: Vec<&str> = names[..err_count].iter().map(String::as_str).collect();
let mut error_decls = String::new();
let mut revert_arms = String::new();
for (i, ename) in err_names.iter().enumerate() {
let n = param_counts[i] as usize;
let decl_params: String = (0..n)
.map(|j| format!("uint256 p{}", j))
.collect::<Vec<_>>()
.join(", ");
let call_args: String = (0..n)
.map(|j| format!("{}", j as u64 + 1))
.collect::<Vec<_>>()
.join(", ");
error_decls.push_str(&format!(" error {}({});\n", ename, decl_params));
revert_arms.push_str(&format!(
" if (which == {i}) {{ revert {name}({args}); }}\n",
i = i,
name = ename,
args = call_args
));
}
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract CustomErrorsFuzz {{
{errors}
function {fn_name}(uint256 which) public pure {{
{arms}
}}
}}"#,
errors = error_decls,
fn_name = fn_name,
arms = revert_arms
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!(
"custom-errors compile failed (count={}, fn={}): {:?}\n--- SOURCE ---\n{}",
err_count, fn_name, e, source
));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
prop_assert!(!artifacts[0].bytecode.is_empty(), "bytecode should be non-empty");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let declared = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(fn_name.as_str())
});
prop_assert!(declared,
"emitting function '{}' missing from manifest; methods={:?}",
fn_name,
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
}
}
#[test]
fn try_catch_three_clauses_compile() {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ThreeClause {
error CustomErr(uint256 code);
function foo() external {
try this.bar() {
} catch Error(string memory s) {
s;
} catch CustomErr(uint256 c) {
c;
} catch (bytes memory lowlevel) {
lowlevel;
}
}
function bar() external pure {}
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("three-clause try/catch compile failed: {:?}", e));
assert!(!artifacts.is_empty(), "expected at least one artifact");
assert!(
!artifacts[0].bytecode.is_empty(),
"bytecode should be non-empty"
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn abi_encode_decode_roundtrip_compile(
contract_name in identifier_strategy()
) {
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract {name} {{
function pack(uint256 a, address b, bytes32 c) external pure returns (bytes memory) {{
return abi.encode(a, b, c);
}}
function unpack(bytes calldata data) external pure returns (uint256, address, bytes32) {{
return abi.decode(data, (uint256, address, bytes32));
}}
}}"#,
name = contract_name
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("abi.encode/decode compile failed: {:?}\n--- SOURCE ---\n{}", e, source));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
prop_assert!(!artifacts[0].bytecode.is_empty(), "bytecode should be non-empty");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let pack = methods.iter().find(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("pack")
});
let unpack = methods.iter().find(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("unpack")
});
prop_assert!(pack.is_some(), "pack missing from manifest; methods={:?}",
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
prop_assert!(unpack.is_some(), "unpack missing from manifest; methods={:?}",
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
let pack_ret = pack.unwrap().get("returntype").and_then(serde_json::Value::as_str);
prop_assert_eq!(pack_ret, Some("ByteArray"),
"pack returntype should be ByteArray, got {:?}", pack_ret);
let unpack_ret = unpack.unwrap().get("returntype").and_then(serde_json::Value::as_str);
prop_assert_eq!(unpack_ret, Some("ByteArray"),
"unpack returntype should be ByteArray for abi-encoded multi-return, got {:?}", unpack_ret);
}
#[test]
fn address_call_staticcall_compile(
payload_arg_count in 0u32..=3,
value in 0u64..=1000,
) {
let sig_types: String = (0..payload_arg_count)
.map(|_| "uint256")
.collect::<Vec<_>>()
.join(",");
let call_args: String = (0..payload_arg_count)
.map(|i| format!(", uint256({})", i + 1))
.collect::<Vec<_>>()
.join("");
let signature = format!("foo({})", sig_types);
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract CallShowcase {{
function doCall(address target) external returns (bool ok, bytes memory data) {{
(ok, data) = target.call{{value: {val}}}(abi.encodeWithSignature("{sig}"{args}));
}}
function doStaticcall(address target) external view returns (bool ok, bytes memory data) {{
(ok, data) = target.staticcall(abi.encodeWithSignature("{sig}"{args}));
}}
}}"#,
val = value,
sig = signature,
args = call_args
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!(
"address.call/staticcall compile failed (argc={}, val={}): {:?}\n--- SOURCE ---\n{}",
payload_arg_count, value, e, source));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
prop_assert!(!artifacts[0].bytecode.is_empty(), "bytecode should be non-empty");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let has_call = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("doCall")
});
let has_static = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("doStaticcall")
});
prop_assert!(has_call, "doCall missing from manifest");
prop_assert!(has_static, "doStaticcall missing from manifest");
let permissions = artifacts[0].manifest["permissions"]
.as_array()
.expect("manifest must expose a permissions array");
prop_assert!(!permissions.is_empty(),
"permissions should be non-empty for a contract making external calls; got {:?}",
permissions);
for perm in permissions {
prop_assert!(perm.get("contract").is_some(),
"permission entry missing 'contract' field: {:?}", perm);
prop_assert!(perm.get("methods").is_some(),
"permission entry missing 'methods' field: {:?}", perm);
}
}
#[test]
fn address_call_opaque_bytes_warns(
_seed in 0u32..=0u32,
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract OpaqueCall {
function doCall(address target, bytes memory payload)
external
returns (bool ok, bytes memory data)
{
(ok, data) = target.call(payload);
}
}"#;
let artifacts = compile_contracts(source, false, 2)
.expect("opaque-bytes call should compile with a warning + runtime trap");
let warnings: Vec<String> = artifacts
.iter()
.flat_map(|a| a.warnings.iter().map(|w| w.message.clone()))
.collect();
let combined = warnings.join("\n").to_lowercase();
prop_assert!(
combined.contains("opaque")
&& combined.contains("not known at compile time")
&& combined.contains("runtime trap"),
"opaque `bytes memory` call must surface a runtime-trap warning; got warnings: {warnings:?}"
);
}
#[test]
fn immutable_and_constant_manifest_exposure(
bar_value in any::<u64>()
) {
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract ImmutConstShowcase {{
uint256 public immutable FOO;
uint256 public constant BAR = {val};
constructor(uint256 initFoo) {{
FOO = initFoo;
}}
}}"#,
val = bar_value
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("immutable+constant compile failed (bar={}): {:?}", bar_value, e));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let foo = methods.iter().find(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("FOO")
});
let bar = methods.iter().find(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("BAR")
});
prop_assert!(foo.is_some(), "FOO (public immutable) missing from manifest; methods={:?}",
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
prop_assert!(bar.is_some(), "BAR (public constant) missing from manifest; methods={:?}",
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
let bar_safe = bar.unwrap().get("safe").and_then(serde_json::Value::as_bool);
prop_assert_eq!(bar_safe, Some(true),
"constant getter BAR should be safe:true (pure/view); got {:?}", bar_safe);
let foo_ret = foo.unwrap().get("returntype").and_then(serde_json::Value::as_str);
let bar_ret = bar.unwrap().get("returntype").and_then(serde_json::Value::as_str);
prop_assert_eq!(foo_ret, Some("Integer"), "FOO returntype should be Integer");
prop_assert_eq!(bar_ret, Some("Integer"), "BAR returntype should be Integer");
}
#[test]
fn ecrecover_cross_reference_via_secp256k1(
fn_name in identifier_strategy(),
sk_bytes in any::<[u8; 32]>(),
hash_bytes in any::<[u8; 32]>(),
) {
use secp256k1::{ecdsa::RecoverableSignature, Message, PublicKey, Secp256k1, SecretKey};
let sk = match SecretKey::from_slice(&sk_bytes) {
Ok(sk) => sk,
Err(_) => { prop_assume!(false); unreachable!(); }
};
let msg = Message::from_slice(&hash_bytes).expect("32 bytes is always a valid Message");
let secp = Secp256k1::new();
let expected_pub: PublicKey = sk.public_key(&secp);
let sig: RecoverableSignature = secp.sign_ecdsa_recoverable(&msg, &sk);
let recovered = secp.recover_ecdsa(&msg, &sig)
.expect("recover_ecdsa must succeed for a freshly-signed message");
prop_assert_eq!(recovered, expected_pub,
"secp256k1 recover_ecdsa must round-trip to the signing pubkey");
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract EcrecoverShowcase {{
function {fname}(bytes32 h, uint8 v, bytes32 r, bytes32 s) public pure returns (address) {{
return ecrecover(h, v, r, s);
}}
}}"#,
fname = fn_name
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("ecrecover wrapper compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
prop_assert!(!artifacts[0].bytecode.is_empty(), "bytecode should be non-empty");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let declared = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(fn_name.as_str())
});
prop_assert!(declared, "ecrecover wrapper '{}' missing from manifest", fn_name);
let create_standard_account_id =
neo_devpack_solidity::interop::interop_id_bytes("System.Contract.CreateStandardAccount");
let bytecode = &artifacts[0].bytecode;
let has_create_account = bytecode
.windows(4)
.any(|w| w == create_standard_account_id);
prop_assert!(!has_create_account,
"ecrecover lowering must not emit System.Contract.CreateStandardAccount \
(Task #20: Ethereum-spec address via keccak256(pubkey[1..])[12..])");
let uses_keccak = artifacts[0]
.tokens
.iter()
.any(|t| t.method == "keccak256")
|| bytecode.windows(9).any(|w| w == b"keccak256");
prop_assert!(uses_keccak,
"ecrecover lowering should invoke CryptoLib.keccak256 on the recovered \
pubkey (Task #20 Ethereum-spec address)");
}
#[test]
fn nef_parse_round_trip_fuzz(
var_name in identifier_strategy(),
literal in any::<u64>(),
) {
use neo_devpack_solidity::neo::{build_nef_with_tokens, parse_nef};
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract NefRoundTripFuzz {{
uint256 public {var} = {lit};
}}"#,
var = var_name,
lit = literal
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("nef-round-trip fuzz compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
let artifact = &artifacts[0];
prop_assert!(!artifact.bytecode.is_empty(), "bytecode should be non-empty");
let compiler = "neo-devpack-solidity-fuzz-batch4";
let source_url = "https://example.test/fuzz-batch-4";
let built = build_nef_with_tokens(&artifact.bytecode, compiler, source_url, &artifact.tokens)
.expect("build_nef_with_tokens must succeed on a compiled artifact");
prop_assert!(built.starts_with(b"NEF3"), "NEF must start with magic NEF3");
let parsed = parse_nef(&built).expect("parse_nef must succeed on a freshly built NEF");
prop_assert_eq!(&parsed.script, &artifact.bytecode,
"parsed.script must equal the original bytecode");
prop_assert_eq!(parsed.tokens.len(), artifact.tokens.len(),
"token count must be preserved through parse_nef");
prop_assert_eq!(&parsed.compiler, compiler, "compiler field round-trip");
prop_assert_eq!(&parsed.source, source_url, "source field round-trip");
let rebuilt = build_nef_with_tokens(&parsed.script, &parsed.compiler, &parsed.source, &parsed.tokens)
.expect("rebuild after parse must succeed");
prop_assert_eq!(rebuilt, built,
"NEF bytes must be byte-identical after parse → rebuild round-trip");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn runtime_getter_returns_initial_value(
n in 0u64..=1_000_000_000u64,
) {
use neo_devpack_solidity::runtime::types::StackItem;
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
uint256 public v = {n};
}}"#,
n = n
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("getter compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.call_method(&artifact.bytecode, &artifact.tokens, &artifact.manifest,
"v", &[] as &[StackItem])
.expect("call_method v() should not error");
prop_assert!(result.success, "v() execution should succeed");
prop_assert_eq!(result.return_data, (n as i64).to_le_bytes().to_vec(),
"v() should return the initializer N={}", n);
}
#[test]
fn runtime_pure_add_matches_rust(
a in 0u64..(1u64 << 62),
b in 0u64..(1u64 << 62),
) {
use neo_devpack_solidity::runtime::types::StackItem;
let sum = a + b;
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function add(uint256 a, uint256 b) external pure returns (uint256) {
return a + b;
}
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("add compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let args = [
StackItem::UnsignedInteger(a),
StackItem::UnsignedInteger(b),
];
let result = runtime
.call_method(&artifact.bytecode, &artifact.tokens, &artifact.manifest,
"add", &args)
.expect("call_method add should not error");
prop_assert!(result.success, "add execution should succeed");
let expected_le = (sum as i64).to_le_bytes().to_vec();
let mut trimmed = expected_le.clone();
while trimmed.last() == Some(&0) {
trimmed.pop();
}
let actual = &result.return_data;
let actual_prefix_len = actual.len().min(expected_le.len());
prop_assert!(
actual[..actual_prefix_len] == expected_le[..actual_prefix_len]
&& actual.iter().skip(expected_le.len()).all(|b| *b == 0)
|| actual == &trimmed,
"add({}, {}) should return {} (LE={:?}); got return_data={:?}",
a, b, sum, expected_le, actual
);
}
#[test]
fn runtime_storage_set_get_roundtrip(
addr_bytes in any::<[u8; 20]>(),
n in 0u64..(1u64 << 62),
) {
use neo_devpack_solidity::runtime::types::StackItem;
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
mapping(address => uint256) public bal;
function set(address a, uint256 x) external { bal[a] = x; }
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("storage compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let set_args = [
StackItem::byte_array(addr_bytes.to_vec()),
StackItem::UnsignedInteger(n),
];
let set_result = runtime
.call_method(&artifact.bytecode, &artifact.tokens, &artifact.manifest, "set", &set_args)
.expect("set should not error");
prop_assert!(set_result.success, "set should succeed");
let get_args = [StackItem::byte_array(addr_bytes.to_vec())];
let get_result = runtime
.call_method(&artifact.bytecode, &artifact.tokens, &artifact.manifest, "bal", &get_args)
.expect("bal should not error");
prop_assert!(get_result.success, "bal should succeed");
prop_assert_eq!(get_result.return_data, (n as i64).to_le_bytes().to_vec(),
"bal({:?}) should return N={}", addr_bytes, n);
}
#[test]
fn runtime_block_height_and_caller_context(
h in 0u64..(1u64 << 62),
caller_hex in any::<[u8; 20]>(),
) {
let height_source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract CHeight {
function height() external view returns (uint256) { return block.number; }
}"#;
let caller_source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract CCaller {
function caller() external view returns (address) { return msg.sender; }
}"#;
let h_artifacts = compile_contracts(height_source, false, 2)
.unwrap_or_else(|e| panic!("height() compile failed: {:?}", e));
let c_artifacts = compile_contracts(caller_source, false, 2)
.unwrap_or_else(|e| panic!("caller() compile failed: {:?}", e));
prop_assert!(!h_artifacts.is_empty() && !c_artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
runtime.override_block_height(h);
let height_result = runtime
.execute(&h_artifacts[0].bytecode, &[])
.expect("height() execute should not error");
prop_assert!(height_result.success, "height() execution must succeed");
prop_assert_eq!(height_result.return_data, (h as i64).to_le_bytes().to_vec(),
"block.number should return override H={}", h);
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let caller_str = format!("0x{}", hex::encode(caller_hex));
runtime
.override_caller_account(&caller_str)
.expect("override_caller_account should accept valid 20-byte hex");
let caller_result = runtime
.execute(&c_artifacts[0].bytecode, &[])
.expect("caller() execute should not error");
prop_assert!(caller_result.success, "caller() execution must succeed");
let mut expected_caller = caller_hex.to_vec();
expected_caller.reverse();
prop_assert_eq!(caller_result.return_data, expected_caller,
"msg.sender should return overridden caller bytes (little-endian)");
}
#[test]
fn runtime_ecrecover_matches_secp256k1(
sk_bytes in any::<[u8; 32]>(),
hash_bytes in any::<[u8; 32]>(),
) {
use neo_devpack_solidity::runtime::types::StackItem;
use secp256k1::{ecdsa::RecoverableSignature, Message, Secp256k1, SecretKey};
use sha3::{Digest, Keccak256};
let sk = match SecretKey::from_slice(&sk_bytes) {
Ok(sk) => sk,
Err(_) => { prop_assume!(false); unreachable!(); }
};
prop_assume!(hash_bytes.iter().any(|b| *b != 0));
let msg = Message::from_slice(&hash_bytes).expect("32 bytes is a valid Message");
let secp = Secp256k1::new();
let sig: RecoverableSignature = secp.sign_ecdsa_recoverable(&msg, &sk);
let (rec_id, sig_compact) = sig.serialize_compact();
let v: u8 = 27 + (rec_id.to_i32() as u8);
let r: [u8; 32] = sig_compact[..32].try_into().expect("r is 32 bytes");
let s: [u8; 32] = sig_compact[32..64].try_into().expect("s is 32 bytes");
let pub_ser = sk.public_key(&secp).serialize_uncompressed(); let mut hasher = Keccak256::new();
hasher.update(&pub_ser[1..]);
let keccak_pub = hasher.finalize();
let expected_addr: [u8; 20] = keccak_pub[12..32].try_into().expect("20 bytes");
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function ecrecover_wrapper(bytes32 h, uint8 v, bytes32 r, bytes32 s) external pure returns (address) {
return ecrecover(h, v, r, s);
}
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("ecrecover wrapper compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let args = [
StackItem::byte_array(hash_bytes.to_vec()),
StackItem::Integer(v as i64),
StackItem::byte_array(r.to_vec()),
StackItem::byte_array(s.to_vec()),
];
let result = runtime
.call_method(&artifact.bytecode, &artifact.tokens, &artifact.manifest,
"ecrecover_wrapper", &args)
.expect("ecrecover_wrapper call_method should not error at the Rust boundary");
prop_assert!(result.success,
"ecrecover_wrapper execution should succeed; got exception {:?}",
result.exception);
prop_assert_eq!(&result.return_data, &expected_addr.to_vec(),
"ecrecover should return the Ethereum address derived from sk; \
expected {} got {}",
hex::encode(expected_addr), hex::encode(&result.return_data));
}
}
#[test]
fn runtime_call_method_reaches_non_first_method() {
use neo_devpack_solidity::runtime::types::StackItem;
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function setX(uint256 _x) external pure returns (uint256) { return _x + 1; }
function getX() external pure returns (uint256) { return 42; }
}"#;
let artifacts = compile_contracts(source, false, 2).expect("compile");
assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let methods = artifact.manifest["abi"]["methods"]
.as_array()
.expect("manifest.abi.methods array");
let get_x_offset = methods
.iter()
.find(|m| m["name"] == "getX")
.and_then(|m| m["offset"].as_u64())
.expect("getX offset");
assert!(
get_x_offset > 0,
"getX must live past bytecode[0] for this test to exercise dispatch; \
got offset={}",
get_x_offset
);
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.call_method(
&artifact.bytecode,
&artifact.tokens,
&artifact.manifest,
"getX",
&[] as &[StackItem],
)
.expect("call_method getX");
assert!(result.success, "getX execution should succeed");
assert_eq!(
result.return_data,
42i64.to_le_bytes().to_vec(),
"getX should return 42; got return_data={:?}",
result.return_data
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn library_using_for_compiles(
lib_name in identifier_strategy(),
contract_name in identifier_strategy(),
method_name in identifier_strategy(),
) {
prop_assume!(lib_name != contract_name);
prop_assume!(method_name != "run");
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
library {lib} {{ function {m}(uint256 x) internal pure returns (uint256) {{ return x * 2; }} }}
contract {c} {{ using {lib} for uint256; function run(uint256 n) external pure returns (uint256) {{ return n.{m}(); }} }}"#,
lib = lib_name, c = contract_name, m = method_name
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("library+using compile failed: {:?}\n--- SOURCE ---\n{}", e, source));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
let contract_artifact = artifacts.iter().find(|a| {
a.manifest["abi"]["methods"].as_array()
.map(|ms| ms.iter().any(|m| m.get("name").and_then(serde_json::Value::as_str) == Some("run")))
.unwrap_or(false)
}).expect("one artifact must declare `run`");
let methods = contract_artifact.manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let run = methods.iter().find(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("run")
}).expect("run method missing");
prop_assert_eq!(run.get("returntype").and_then(serde_json::Value::as_str),
Some("Integer"), "run returntype should be Integer for uint256");
let leaked = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some(method_name.as_str())
});
prop_assert!(!leaked,
"library helper '{}' should be inlined, not surfaced as a contract method; methods={:?}",
method_name,
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
}
#[test]
fn delegatecall_hard_rejected_at_compile_time(
fn_name in identifier_strategy(),
) {
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
function {f}(address target, bytes calldata data) external returns (bool, bytes memory) {{
return target.delegatecall(data);
}}
}}"#,
f = fn_name
);
let artifacts = compile_contracts(&source, false, 2)
.expect("delegatecall should compile with warning + runtime trap (v0.19.0)");
let warnings: Vec<String> = artifacts
.iter()
.flat_map(|a| a.warnings.iter().map(|w| w.message.clone()))
.collect();
let combined = warnings.join("\n").to_lowercase();
prop_assert!(
combined.contains("delegatecall") && combined.contains("not supported"),
"expected delegatecall warning for fn '{}'; got warnings: {:?}",
fn_name, warnings
);
let bytecode_contains_abortmsg = artifacts
.iter()
.any(|a| a.bytecode.contains(&0xE0));
prop_assert!(
bytecode_contains_abortmsg,
"delegatecall should lower to ABORTMSG (0xE0) at the trap site for fn '{}'",
fn_name
);
}
#[test]
fn receive_and_fallback_manifest_methods(
contract_name in identifier_strategy(),
) {
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract {c} {{
event Received(address s, uint256 v, bytes d);
receive() external payable {{ emit Received(msg.sender, msg.value, ""); }}
fallback() external payable {{ emit Received(msg.sender, msg.value, msg.data); }}
}}"#,
c = contract_name
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("receive+fallback compile failed: {:?}\n--- SOURCE ---\n{}", e, source));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let names: Vec<&str> = methods.iter()
.filter_map(|m| m.get("name").and_then(serde_json::Value::as_str))
.collect();
prop_assert!(names.contains(&"onNEP17Payment"),
"expected `receive()` to be remapped to `onNEP17Payment`; got methods={:?}", names);
prop_assert!(names.contains(&"fallback"),
"expected `fallback` in manifest; got methods={:?}", names);
}
#[test]
fn inline_assembly_noop_compiles(
use_simple_body in any::<bool>(),
) {
let body = if use_simple_body {
"assembly { let x := 1 let y := add(x, 2) }"
} else {
"assembly { }"
};
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function nop() external pure returns (uint256) {{ {body} return 0; }} }}"#,
body = body
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("assembly no-op compile failed (simple={}): {:?}\n--- SOURCE ---\n{}",
use_simple_body, e, source));
prop_assert!(!artifacts.is_empty(), "expected at least one artifact");
let methods = artifacts[0].manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let has_nop = methods.iter().any(|m| {
m.get("name").and_then(serde_json::Value::as_str) == Some("nop")
});
prop_assert!(has_nop,
"nop missing from manifest (simple={}); methods={:?}",
use_simple_body,
methods.iter().map(|m| m.get("name").cloned()).collect::<Vec<_>>());
use neo_devpack_solidity::runtime::types::StackItem;
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.call_method(&artifacts[0].bytecode, &artifacts[0].tokens, &artifacts[0].manifest,
"nop", &[] as &[StackItem])
.expect("nop call_method should not error at the Rust boundary");
prop_assert!(result.success,
"nop execution should succeed (simple={}); got exception {:?}",
use_simple_body, result.exception);
let observed = decode_uint_le(&result.return_data);
prop_assert_eq!(&observed, &num_bigint::BigUint::from(0u8),
"nop() must return 0 (simple={}); return_data={:?}",
use_simple_body, result.return_data);
}
}
#[test]
fn internal_function_type_as_storage_variable_compile() {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function(uint256) internal pure returns (uint256) public op;
function square(uint256 x) internal pure returns (uint256) { return x * x; }
constructor() { op = square; }
function apply(uint256 n) external view returns (uint256) { return op(n); }
}"#;
let result = compile_contracts(source, false, 2);
let err = match result {
Err(e) => format!("{:?}", e),
Ok(_) => panic!(
"compiler unexpectedly accepted a function-typed state variable; \
if function-type support has been added, update \
docs/SOLIDITY_SUPPORT_MATRIX.md §A and rewrite this test to assert success"
),
};
assert!(
err.contains("unsupported type") && err.contains("function"),
"expected 'unsupported type ... function ...' diagnostic for function-typed \
state variable, got: {err}"
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn parse_nef_rejects_malformed_magic(
replacement in any::<[u8; 4]>(),
) {
use neo_devpack_solidity::neo::{build_nef_with_tokens, parse_nef};
use sha2::{Digest, Sha256};
prop_assume!(replacement != *b"NEF3");
let script = vec![0x10u8, 0x40u8];
let mut nef = build_nef_with_tokens(&script, "batch7", "", &[])
.expect("valid NEF should build");
nef[..4].copy_from_slice(&replacement);
let n = nef.len();
let prefix_hash = Sha256::digest(Sha256::digest(&nef[..n - 4]));
nef[n - 4..].copy_from_slice(&prefix_hash[..4]);
let err = parse_nef(&nef).expect_err("parse_nef must reject bad magic");
prop_assert!(
err.to_lowercase().contains("magic"),
"error must mention magic; got: {}", err
);
}
#[test]
fn parse_nef_rejects_bad_checksum(
idx_seed in any::<u32>(),
replacement in any::<u8>(),
) {
use neo_devpack_solidity::neo::{build_nef_with_tokens, parse_nef};
let script = vec![0x10u8, 0x40u8];
let nef = build_nef_with_tokens(&script, "batch7", "", &[])
.expect("valid NEF should build");
let lo = 4usize;
let hi = nef.len() - 4;
prop_assume!(lo < hi);
let idx = lo + (idx_seed as usize) % (hi - lo);
let original = nef[idx];
prop_assume!(replacement != original);
let mut mutated = nef.clone();
mutated[idx] = replacement;
let err = parse_nef(&mutated).expect_err("parse_nef must reject checksum mismatch");
prop_assert!(
err.to_lowercase().contains("checksum"),
"expected 'checksum' in error for prefix byte mutation at idx={idx}; got: {err}"
);
}
#[test]
fn parse_nef_handles_truncation(
len_seed in any::<u32>(),
) {
use neo_devpack_solidity::neo::{build_nef_with_tokens, parse_nef};
let script = vec![0x10u8, 0x40u8];
let nef = build_nef_with_tokens(&script, "batch7", "", &[])
.expect("valid NEF should build");
let total = nef.len();
prop_assume!(total > 4);
let trunc_len = 4 + (len_seed as usize) % (total - 4);
let truncated = &nef[..trunc_len];
let result = parse_nef(truncated);
prop_assert!(result.is_err(),
"parse_nef must reject truncated input (len={trunc_len}/{total})");
let err = result.unwrap_err();
prop_assert!(!err.is_empty(), "error message must be non-empty");
}
#[test]
fn runtime_keccak256_matches_sha3(
_nonce in any::<u32>(),
) {
use sha3::{Digest, Keccak256};
const FIXED_HEX: &str = "deadbeefcafef00d";
let fixed_bytes = hex::decode(FIXED_HEX).expect("valid hex literal");
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function h() external pure returns (bytes32) {
return keccak256(hex"deadbeefcafef00d");
}
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("keccak256 single-fn compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let methods = artifact.manifest["abi"]["methods"]
.as_array()
.expect("abi.methods array");
let h_method = methods
.iter()
.find(|m| m.get("name").and_then(serde_json::Value::as_str) == Some("h"))
.expect("manifest must declare method `h`");
prop_assert_eq!(
h_method.get("returntype").and_then(serde_json::Value::as_str),
Some("Hash256"),
"keccak256-returning `h` must have returntype Hash256 in manifest"
);
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of keccak256 contract should not error");
prop_assert!(result.success, "keccak256 execution must succeed");
let expected = Keccak256::digest(&fixed_bytes).to_vec();
prop_assert_eq!(result.return_data, expected,
"keccak256(hex\"{}\") must equal sha3::Keccak256::digest", FIXED_HEX);
}
#[test]
fn runtime_timestamp_override_visible_in_view(
t_seconds in 0u64..2_000_000_000u64,
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function ts() external view returns (uint256) { return block.timestamp; }
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("block.timestamp single-fn compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
runtime.override_timestamp(t_seconds.saturating_mul(1000));
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of block.timestamp contract should not error");
prop_assert!(result.success, "block.timestamp execution must succeed");
prop_assert_eq!(result.return_data, (t_seconds as i64).to_le_bytes().to_vec(),
"block.timestamp must reflect override_timestamp(T*1000) / 1000 = {} seconds",
t_seconds);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn runtime_gas_accounting_bounded(
_nonce in any::<u32>(),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function ping() external pure returns (uint256) { return 1; } }"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("ping compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let config = RuntimeConfig::default();
let limit_ceiling = config.gas_limit;
let mut runtime = NeoRuntime::new(config).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of ping contract should not error");
prop_assert!(result.success, "ping execution must succeed");
prop_assert!(result.gas_used > 0,
"gas_used must be positive for any executed contract; got {}",
result.gas_used);
prop_assert!(result.gas_used < limit_ceiling,
"gas_used ({}) must be strictly below RuntimeConfig::default().gas_limit ({})",
result.gas_used, limit_ceiling);
prop_assert_eq!(result.gas_limit, limit_ceiling,
"result.gas_limit must echo the configured limit");
}
#[test]
fn runtime_event_emission_captured(
n in 0u64..=1_000_000u64,
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
event Ping(uint256 n);
function go() external {{ emit Ping({n}); }}
}}"#, n = n);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("event compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of event-emitting contract should not error");
prop_assert!(result.success, "event-emitting execution must succeed");
prop_assert_eq!(result.logs.len(), 1,
"exactly one Notify/LogEntry must be captured; got {}",
result.logs.len());
let log = &result.logs[0];
prop_assert_eq!(log.topics.len(), 1,
"Ping has 0 indexed args — exactly 1 topic (the signature hash) \
expected; got {}", log.topics.len());
use sha3::{Digest, Keccak256};
let mut hasher = Keccak256::new();
hasher.update(b"Ping(uint256)");
let expected_sig_hash = hasher.finalize();
prop_assert_eq!(log.topics[0].len(), 32,
"topics[0] must be 32 bytes (keccak256 sig hash); got {} bytes",
log.topics[0].len());
prop_assert_eq!(&log.topics[0][..], &expected_sig_hash[..],
"topics[0] must equal keccak256(\"Ping(uint256)\"); got {:?}",
hex::encode(&log.topics[0]));
prop_assert_eq!(log.data.len(), 32,
"data must be exactly 32 bytes (abi.encode of a single uint256); got {}",
log.data.len());
let mut expected_data = [0u8; 32];
expected_data[24..].copy_from_slice(&n.to_be_bytes());
prop_assert_eq!(&log.data[..], &expected_data[..],
"data must be BE32(n); got {:?}", hex::encode(&log.data));
}
#[test]
fn runtime_revert_custom_error_produces_error_result(
x in 0u64..=1_000u64,
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
error TooSmall(uint256 x);
function boom() external pure {{ revert TooSmall({x}); }}
}}"#, x = x);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("revert compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of revert contract should not error (revert != panic)");
prop_assert!(!result.success,
"a `revert` must surface as `success == false`");
let exception = result.exception.as_ref()
.expect("revert must populate `exception`");
let ty = exception.exception_type.as_str();
prop_assert_eq!(ty, "RevertExecution",
"revert must yield RevertExecution (not Fault); got {}", ty);
prop_assert!(exception.message.contains("THROW"),
"revert message must carry the THROW marker; got {:?}",
exception.message);
use sha3::{Digest, Keccak256};
let mut hasher = Keccak256::new();
hasher.update(b"TooSmall(uint256)");
let digest = hasher.finalize();
let expected_selector = &digest[..4];
prop_assert_eq!(result.return_data.len(), 36,
"custom-error revert payload must be 4-byte selector + 32-byte \
abi.encode(uint256) = 36 bytes; got {} bytes (data={:02x?})",
result.return_data.len(), result.return_data);
prop_assert_eq!(&result.return_data[..4], expected_selector,
"return_data prefix must equal keccak256(\"TooSmall(uint256)\")[0..4] \
= {:02x?}; got {:02x?}",
expected_selector, &result.return_data[..4]);
let mut expected_arg = [0u8; 32];
expected_arg[24..].copy_from_slice(&x.to_be_bytes());
prop_assert_eq!(&result.return_data[4..36], &expected_arg[..],
"return_data tail must equal abi.encode({}) = BE32(x); got {:02x?}",
x, &result.return_data[4..36]);
}
#[test]
fn storage_packed_uint8_layout_manifest(
va in 0u8..=u8::MAX,
vb in 0u8..=u8::MAX,
vc in 0u64..=1_000_000u64,
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
uint8 public a = {va};
uint8 public b = {vb};
uint256 public c = {vc};
}}"#, va = va, vb = vb, vc = vc);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("storage-layout compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let methods = artifact.manifest["abi"]["methods"]
.as_array()
.expect("abi.methods must be an array");
let user_methods: Vec<&serde_json::Value> = methods
.iter()
.filter(|m| m.get("name").and_then(serde_json::Value::as_str) != Some("_deploy"))
.collect();
prop_assert_eq!(user_methods.len(), 3,
"exactly three user-visible getters expected (a, b, c); got {}",
user_methods.len());
for name in ["a", "b", "c"] {
let method = user_methods.iter()
.find(|m| m.get("name").and_then(serde_json::Value::as_str) == Some(name))
.unwrap_or_else(|| panic!("manifest must expose getter `{}`", name));
prop_assert_eq!(
method.get("returntype").and_then(serde_json::Value::as_str),
Some("Integer"),
"getter `{}` must have returntype Integer (uint{{8,256}} both map to Integer)",
name
);
let params = method.get("parameters").and_then(serde_json::Value::as_array)
.unwrap_or_else(|| panic!("getter `{}` must have a `parameters` array", name));
prop_assert!(params.is_empty(),
"public getter `{}` must take zero arguments; got {:?}", name, params);
}
}
#[test]
fn nep17_manifest_compliance_declared_standards(
contract_name in identifier_strategy(),
) {
prop_assume!(contract_name != "_deploy");
prop_assume!(contract_name != "C");
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/// @custom:neo.manifest.supportedstandards ["NEP-17"]
contract {cn} {{
event Transfer(address indexed from, address indexed to, uint256 amount);
function symbol() external pure returns (string memory) {{ return "FUZ"; }}
function decimals() external pure returns (uint8) {{ return 8; }}
function totalSupply() external view returns (uint256) {{ return 0; }}
function balanceOf(address) external view returns (uint256) {{ return 0; }}
function transfer(address from, address to, uint256 amount, bytes calldata data) external returns (bool) {{ emit Transfer(from, to, amount); return false; }}
}}"#, cn = contract_name);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("NEP-17 stub compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let bad_source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/// @custom:neo.manifest.supportedstandards ["NEP-17"]
contract {cn} {{
function symbol() external pure returns (string memory) {{ return "FUZ"; }}
function decimals() external pure returns (uint8) {{ return 8; }}
function totalSupply() external view returns (uint256) {{ return 0; }}
function balanceOf(address) external view returns (uint256) {{ return 0; }}
function transfer(address from, address to, uint256 amount, bytes calldata data) external returns (bool) {{ return false; }}
}}"#, cn = contract_name);
let bad = compile_contracts(&bad_source, false, 2);
prop_assert!(
bad.is_err(),
"declaring NEP-17 without a Transfer event must fail, but compile succeeded"
);
prop_assert_eq!(
artifact.manifest["name"].as_str(),
Some(contract_name.as_str()),
"manifest.name must match source contract name"
);
let standards = artifact.manifest["supportedstandards"].as_array()
.expect("supportedstandards must be an array");
prop_assert!(
standards.iter().any(|s| s.as_str() == Some("NEP-17")),
"supportedstandards must advertise NEP-17; got {:?}", standards
);
let methods = artifact.manifest["abi"]["methods"].as_array()
.expect("abi.methods must be an array");
let method_names: std::collections::HashSet<&str> = methods.iter()
.filter_map(|m| m.get("name").and_then(serde_json::Value::as_str))
.collect();
for required in ["symbol", "decimals", "totalSupply", "balanceOf", "transfer"] {
prop_assert!(method_names.contains(required),
"manifest must expose NEP-17 method `{}`; got {:?}",
required, method_names);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn runtime_checked_add_overflows_revert(
b in any::<u128>(),
) {
use num_bigint::BigUint;
use num_traits::Num;
let u256_max = BigUint::from_str_radix(
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
16,
).expect("u256 max literal must parse");
let overflows = b > 0;
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{
uint256 a = type(uint256).max;
return a + {b};
}} }}"#,
b = b);
let result = compile_and_execute(&source);
if overflows {
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"checked add overflow (u256::MAX + {}) must revert with \
Panic(0x11); result={:?}", b, result);
} else {
prop_assert!(result.success,
"non-overflowing checked add (u256::MAX + 0) must succeed; \
got {:?}", result.exception);
let actual = decode_uint_le(&result.return_data);
prop_assert_eq!(&actual, &u256_max,
"checked add(u256::MAX, 0) must return u256::MAX (decoded from {:?})",
result.return_data);
}
}
#[test]
fn runtime_unchecked_wraps_modular(
a in any::<u128>(),
b in any::<u128>(),
) {
use num_bigint::BigUint;
use num_traits::{Num, One};
let mod_2_256 = BigUint::from_str_radix(
"10000000000000000000000000000000000000000000000000000000000000000",
16,
).expect("2^256 literal must parse");
prop_assert!(mod_2_256 > BigUint::one());
let a_bi = BigUint::from(a);
let b_bi = BigUint::from(b);
let expected = (&a_bi + &b_bi) % &mod_2_256;
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{ unchecked {{ return {a} + {b}; }} }} }}"#,
a = a, b = b);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("unchecked-add compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of unchecked-add contract should not error");
prop_assert!(result.success,
"unchecked add must always succeed; got exception {:?}", result.exception);
let actual = decode_uint_le(&result.return_data);
prop_assert_eq!(&actual, &expected,
"unchecked add({}, {}) mod 2^256 must equal {} (decoded from {:?})",
a, b, expected, result.return_data);
}
#[test]
fn runtime_division_by_zero_reverts(
a in any::<u128>(),
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{ uint256 z = 0; return {a} / z; }} }}"#,
a = a);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("div-by-zero compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of div-by-zero contract should not error (div0 != host-error)");
prop_assert!(!result.success,
"division by zero must produce success == false");
let exc = result.exception.as_ref()
.expect("division by zero must populate `exception`");
let ty = exc.exception_type.as_str();
prop_assert_eq!(ty, "RevertExecution",
"div-by-zero (Solidity Panic(0x12)) must yield RevertExecution; got {}", ty);
let rd = &result.return_data;
prop_assert!(
rd.len() >= 36 && &rd[..4] == &[0x4eu8, 0x48, 0x7b, 0x71] && rd[35] == 0x12,
"div-by-zero revert payload must be keccak('Panic(uint256)')[..4] || abi.encode(0x12); \
got rd_len={} rd_hex={} msg={:?}",
rd.len(), hex::encode(rd), exc.message);
}
#[test]
fn compile_and_invoke_bytes_length(
data in prop::collection::vec(any::<u8>(), 0..=64),
) {
use num_bigint::BigUint;
let hex_str = hex::encode(&data);
prop_assert_eq!(hex_str.len() % 2, 0,
"hex::encode must produce even-length output");
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{ bytes memory b = hex"{hex}"; return b.length; }} }}"#,
hex = hex_str);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("bytes-length compile failed (hex='{}'): {:?}", hex_str, e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of bytes-length contract should not error");
prop_assert!(result.success,
"bytes-length execution must succeed; got exception {:?}", result.exception);
let actual = decode_uint_le(&result.return_data);
let expected = BigUint::from(data.len() as u64);
prop_assert_eq!(&actual, &expected,
"hex{:?}.length must equal {} (decoded from {:?})",
hex_str, data.len(), result.return_data);
}
#[test]
fn compile_and_invoke_string_concat_length(
a in "[A-Za-z0-9_]{0,32}",
b in "[A-Za-z0-9_]{0,32}",
) {
use num_bigint::BigUint;
prop_assume!(!a.contains('"') && !a.contains('\\'));
prop_assume!(!b.contains('"') && !b.contains('\\'));
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{
string memory a = "{a}";
string memory b = "{b}";
return bytes(string.concat(a, b)).length;
}} }}"#, a = a, b = b);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("string.concat compile failed (a={:?}, b={:?}): {:?}", a, b, e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("execute of string.concat contract should not error");
prop_assert!(result.success,
"string.concat execution must succeed; got exception {:?}", result.exception);
let actual = decode_uint_le(&result.return_data);
let expected = BigUint::from((a.len() + b.len()) as u64);
prop_assert_eq!(&actual, &expected,
"bytes(string.concat({:?}, {:?})).length must equal {} (decoded from {:?})",
a, b, a.len() + b.len(), result.return_data);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn arith_scope_uint256_add_at_max(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 a = type(uint256).max;
return a + 1;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_uint256_add_at_max: expected Panic(0x11) after Task #30 slice 1");
}
#[test]
fn arith_scope_uint256_sub_underflow(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 a = 0;
return a - 1;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_uint256_sub_underflow: expected Panic(0x11) after Task #30 slice 2");
}
#[test]
fn arith_scope_uint256_mul_overflow(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 a = type(uint256).max / 2 + 1;
return a * 2;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_uint256_mul_overflow: expected Panic(0x11) after Task #30 slice 2");
}
#[test]
fn arith_scope_uint256_add_narrow_boundary(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 a = 0x7FFFFFFFFFFFFFFF;
return a + 1;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed,
ObservedBehavior::Returned(num_bigint::BigUint::from(1u64) << 63),
"arith_scope_uint256_add_narrow_boundary: expected Returned(2^63) after Task #30 slice 3");
}
#[test]
fn arith_scope_uint256_mul_mixed_narrow(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 big = type(uint256).max;
return big * 2;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_uint256_mul_mixed_narrow: expected Panic(0x11) via slice 3 widening");
}
#[test]
fn arith_scope_uint256_div_by_zero(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 a = 100;
uint256 b = 0;
return a / b;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x12),
"div by zero MUST panic with 0x12 (positive control)");
}
#[test]
fn arith_scope_uint256_mod_by_zero(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 a = 100;
uint256 b = 0;
return a % b;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x12),
"mod by zero MUST panic with 0x12");
}
#[test]
fn arith_scope_int256_negate_min(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
int256 a = type(int256).min;
return uint256(-a);
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_int256_negate_min: expected Panic(0x11) after Task #30 slice 2");
}
#[test]
fn arith_scope_uint8_downcast_overflow(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 big = 300;
return uint256(uint8(big));
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Returned(num_bigint::BigUint::from(44u8)),
"uint8(300) must truncate to 44 (300 mod 256), not panic");
}
#[test]
fn arith_scope_shift_left_loss(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 a = 1;
return a << 256;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Returned(num_bigint::BigUint::from(0u8)),
"arith_scope_shift_left_loss: expected Returned(0) per EIP-145 after Task #33");
}
#[test]
fn arith_scope_unchecked_wraps(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
unchecked {
uint256 a = type(uint256).max;
return a + 1;
}
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
match observed {
ObservedBehavior::Returned(ref n)
if n == &num_bigint::BigUint::from(0u8) =>
{
}
ObservedBehavior::Returned(ref n)
if n == &(num_bigint::BigUint::from(1u8) << 256) =>
{
}
other => prop_assert!(false,
"arith_scope_unchecked_wraps: unexpected behavior {:?}", other),
}
}
#[test]
fn arith_scope_increment_at_max(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256 a = type(uint256).max;
a++;
return a;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_increment_at_max: expected Panic(0x11) after Task #30 slice 4");
}
#[test]
fn arith_scope_int256_add_overflow(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (int256) {
int256 a = type(int256).max;
return a + 1;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_int256_add_overflow: expected Panic(0x11) after Task #67");
}
#[test]
fn arith_scope_int256_sub_underflow(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (int256) {
int256 a = type(int256).min;
return a - 1;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_int256_sub_underflow: expected Panic(0x11) after Task #67");
}
#[test]
fn arith_scope_int256_mul_overflow(_seed in any::<u8>()) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (int256) {
int256 a = type(int256).max;
return a * 2;
} }"#;
let result = compile_and_execute(source);
let observed = observe(&result);
prop_assert_eq!(observed, ObservedBehavior::Panicked(0x11),
"arith_scope_int256_mul_overflow: expected Panic(0x11) after Task #67");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn runtime_getrandom_syscall_returns_bytes(
h in 1u64..(1u64 << 40),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function r() external view returns (uint256) { return Runtime.getRandom(); }
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("Runtime.getRandom() compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
runtime.override_block_height(h);
let result = runtime
.execute(&artifacts[0].bytecode, &[])
.expect("getRandom() execute should not error at host level");
prop_assert!(result.success, "getRandom execution must succeed: {:?}",
result.exception.as_ref().map(|e| &e.message));
prop_assert_eq!(result.return_data.len(), 32,
"GetRandom pushes a 32-byte SHA-256 digest; got {} bytes",
result.return_data.len());
prop_assert!(result.return_data.iter().any(|b| *b != 0),
"32-byte SHA-256 digest of (seed||0) for height={} should not be all zeros \
(probability ~2^-256)", h);
}
#[test]
fn runtime_checkwitness_without_signature_returns_false(
addr_bytes in any::<[u8; 20]>(),
) {
prop_assume!(addr_bytes.iter().any(|b| *b != 0));
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
function w() external view returns (bool) {{
return Runtime.checkWitness(address(0x{}));
}}
}}"#,
hex::encode(addr_bytes)
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("checkWitness compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifacts[0].bytecode, &[])
.expect("checkWitness execute should not error at host level");
prop_assert!(result.success, "checkWitness execution must succeed: {:?}",
result.exception.as_ref().map(|e| &e.message));
prop_assert_eq!(&result.return_data, &vec![0u8],
"checkWitness(0x{}) with empty signers + [0;20] caller/default \
should return Boolean(false) encoded as [0]; got {:?}",
hex::encode(addr_bytes), result.return_data);
}
#[test]
fn runtime_gettime_override_visible(
t_ms in 1u64..(1u64 << 50),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function t() external view returns (uint256) { return Runtime.getTime(); }
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("Runtime.getTime() compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
runtime.override_timestamp(t_ms);
let result = runtime
.execute(&artifacts[0].bytecode, &[])
.expect("getTime execute should not error at host level");
prop_assert!(result.success, "getTime execution must succeed: {:?}",
result.exception.as_ref().map(|e| &e.message));
prop_assert_eq!(result.return_data, t_ms.to_le_bytes().to_vec(),
"Runtime.getTime() must return T_MS={} unchanged (NO /1000), \
unlike block.timestamp. stack_item_to_bytes(UnsignedInteger(t)) \
emits exactly t.to_le_bytes()", t_ms);
}
#[test]
fn runtime_notify_emits_log_with_custom_event(
val in any::<u64>(),
) {
let val_lit = if val > i64::MAX as u64 { i64::MAX as u64 } else { val };
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
event Custom(string name, uint256 val);
function go() external {{ emit Custom("fuzz", {}); }}
}}"#,
val_lit
);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("emit Custom compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifacts[0].bytecode, &[])
.expect("emit Custom execute should not error at host level");
prop_assert!(result.success, "emit Custom execution must succeed: {:?}",
result.exception.as_ref().map(|e| &e.message));
prop_assert_eq!(result.logs.len(), 1,
"System.Runtime.Notify must produce exactly one LogEntry; got {}",
result.logs.len());
let entry = &result.logs[0];
prop_assert_eq!(entry.topics.len(), 1,
"Custom has 0 indexed args — topics must be [sig] only; got {}",
entry.topics.len());
use sha3::{Digest, Keccak256};
let mut hasher = Keccak256::new();
hasher.update(b"Custom(string,uint256)");
let expected_topic0 = hasher.finalize();
prop_assert_eq!(&entry.topics[0][..], &expected_topic0[..],
"topics[0] must be keccak256(\"Custom(string,uint256)\"); got {}",
hex::encode(&entry.topics[0]));
prop_assert_eq!(entry.data.len(), 128,
"data must be 128 bytes (EVM-spec head + tail for string + uint256); got {}",
entry.data.len());
let mut expected_off0 = [0u8; 32];
expected_off0[31] = 0x40;
prop_assert_eq!(&entry.data[0..32], &expected_off0[..],
"data[0..32] must be offset 0x40; got {}", hex::encode(&entry.data[0..32]));
let mut expected_val = [0u8; 32];
expected_val[24..].copy_from_slice(&val_lit.to_be_bytes());
prop_assert_eq!(&entry.data[32..64], &expected_val[..],
"data[32..64] must be BE32(val); got {}", hex::encode(&entry.data[32..64]));
let mut expected_len = [0u8; 32];
expected_len[31] = 0x04;
prop_assert_eq!(&entry.data[64..96], &expected_len[..],
"data[64..96] must be length 4 for 'fuzz'; got {}",
hex::encode(&entry.data[64..96]));
prop_assert_eq!(&entry.data[96..100], b"fuzz",
"data[96..100] must be 'fuzz' left-aligned; got {}",
hex::encode(&entry.data[96..128]));
}
#[test]
fn runtime_contract_hash_stable_across_calls(
var_name in identifier_strategy(),
sender_bytes in any::<[u8; 20]>(),
) {
use neo_devpack_solidity::neo::{build_nef_with_tokens, compute_contract_hash};
let source = format!(
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract HashStable {{
function v() external pure returns (uint256) {{ return {} + 1; }}
}}"#,
42u64 );
let arts_a = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("first compile failed: {:?}", e));
let arts_b = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("second compile failed: {:?}", e));
prop_assert!(!arts_a.is_empty() && !arts_b.is_empty());
let art_a = &arts_a[0];
let art_b = &arts_b[0];
prop_assert_eq!(&art_a.bytecode, &art_b.bytecode,
"same source must produce byte-identical bytecode");
let nef_a = build_nef_with_tokens(&art_a.bytecode, "neo-devpack-solidity-batch11",
"batch11", &art_a.tokens).expect("build_nef_a");
let nef_b = build_nef_with_tokens(&art_b.bytecode, "neo-devpack-solidity-batch11",
"batch11", &art_b.tokens).expect("build_nef_b");
prop_assert_eq!(&nef_a, &nef_b, "NEFs must be byte-identical");
prop_assert!(nef_a.len() > 4, "NEF must have a trailer");
let checksum_a = u32::from_le_bytes(
nef_a[nef_a.len() - 4..].try_into().expect("4 bytes"));
let checksum_b = u32::from_le_bytes(
nef_b[nef_b.len() - 4..].try_into().expect("4 bytes"));
prop_assert_eq!(checksum_a, checksum_b,
"byte-identical NEFs must have equal checksums");
let name = format!("HashStable_{}", var_name);
let hash_a = compute_contract_hash(sender_bytes, checksum_a, &name);
let hash_b = compute_contract_hash(sender_bytes, checksum_b, &name);
prop_assert_eq!(hash_a, hash_b,
"compute_contract_hash is a pure function; identical inputs \
must yield identical 20-byte script hashes");
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn nep11_manifest_compliance_declared_standards(
contract_name in identifier_strategy(),
) {
prop_assume!(contract_name != "_deploy");
prop_assume!(contract_name != "N");
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/// @custom:neo.manifest.supportedstandards ["NEP-11"]
contract {cn} {{
event Transfer(address indexed from, address indexed to, uint256 amount, bytes tokenId);
function symbol() external pure returns (string memory) {{ return "FUZZ11"; }}
function decimals() external pure returns (uint8) {{ return 0; }}
function totalSupply() external view returns (uint256) {{ return 0; }}
function balanceOf(address owner) external view returns (uint256) {{ return 0; }}
function tokensOf(address owner) external view returns (bytes memory) {{ return ""; }}
function ownerOf(bytes memory tokenId) external view returns (address) {{ return address(0); }}
function transfer(address to, bytes memory tokenId, bytes memory data) external returns (bool) {{
emit Transfer(msg.sender, to, 1, tokenId);
return false;
}}
function properties(bytes memory tokenId) external view returns (string memory) {{ return "{{}}"; }}
}}"#, cn = contract_name);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("NEP-11 stub compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
prop_assert_eq!(
artifact.manifest["name"].as_str(),
Some(contract_name.as_str()),
"manifest.name must echo fuzzed contract name"
);
let standards = artifact.manifest["supportedstandards"].as_array()
.expect("supportedstandards must be an array");
prop_assert!(
standards.iter().any(|s| s.as_str() == Some("NEP-11")),
"supportedstandards must advertise NEP-11; got {:?}", standards
);
let methods = artifact.manifest["abi"]["methods"].as_array()
.expect("abi.methods must be an array");
let method_names: std::collections::HashSet<&str> = methods.iter()
.filter_map(|m| m.get("name").and_then(serde_json::Value::as_str))
.collect();
for required in [
"symbol", "decimals", "totalSupply", "balanceOf",
"tokensOf", "ownerOf", "transfer", "properties",
] {
prop_assert!(method_names.contains(required),
"manifest must expose NEP-11 method `{}`; got {:?}",
required, method_names);
}
}
#[test]
fn runtime_checkmultisig_without_signers_returns_false(
contract_name in identifier_strategy(),
) {
prop_assume!(contract_name != "_deploy");
prop_assume!(contract_name != "C");
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract {cn} {{
function m() external view returns (bool) {{
bytes[] memory pks = new bytes[](0);
bytes[] memory sigs = new bytes[](0);
return Syscalls.checkMultisig(pks, sigs);
}}
}}"#, cn = contract_name);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("checkMultisig compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let methods = artifact.manifest["abi"]["methods"].as_array()
.expect("abi.methods must be an array");
let m = methods.iter()
.find(|m| m.get("name").and_then(serde_json::Value::as_str) == Some("m"))
.expect("method `m` must exist in manifest");
prop_assert_eq!(m.get("returntype").and_then(serde_json::Value::as_str),
Some("Boolean"),
"checkMultisig wrapper must declare Boolean returntype; got {:?}",
m.get("returntype"));
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifact.bytecode, &[])
.expect("checkMultisig execute must not fail at host level");
prop_assert!(result.success,
"checkMultisig execution must succeed: {:?}",
result.exception.as_ref().map(|e| &e.message));
prop_assert_eq!(&result.return_data, &vec![0u8],
"Syscalls.checkMultisig(empty, empty) must return Boolean(false) \
encoded as [0]; got {:?}", result.return_data);
}
#[test]
fn storage_namespace_isolation_across_contracts(
va in 0u64..=1_000_000u64,
vb in 0u64..=1_000_000u64,
sender_bytes in any::<[u8; 20]>(),
) {
use neo_devpack_solidity::neo::{build_nef_with_tokens, compute_contract_hash};
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract A {{ uint256 public v = {}; }}
contract B {{ uint256 public v = {}; }}"#, va, vb);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("two-contract compile failed: {:?}", e));
prop_assert_eq!(artifacts.len(), 2,
"two-contract source must produce two artifacts; got {}",
artifacts.len());
let art_a = artifacts.iter()
.find(|a| a.manifest["name"].as_str() == Some("A"))
.expect("contract A must be present in artifacts");
let art_b = artifacts.iter()
.find(|a| a.manifest["name"].as_str() == Some("B"))
.expect("contract B must be present in artifacts");
for (tag, art) in [("A", art_a), ("B", art_b)] {
let methods = art.manifest["abi"]["methods"].as_array()
.expect("abi.methods must be an array");
let v_count = methods.iter()
.filter(|m| m.get("name").and_then(serde_json::Value::as_str) == Some("v"))
.count();
prop_assert_eq!(v_count, 1,
"contract {} must export exactly one `v` method; got {}",
tag, v_count);
}
let nef_a = build_nef_with_tokens(&art_a.bytecode, "neo-devpack-solidity-batch12",
"batch12", &art_a.tokens).expect("build_nef A");
let nef_b = build_nef_with_tokens(&art_b.bytecode, "neo-devpack-solidity-batch12",
"batch12", &art_b.tokens).expect("build_nef B");
prop_assert!(nef_a.len() > 4 && nef_b.len() > 4,
"NEFs must carry a trailer");
let checksum_a = u32::from_le_bytes(
nef_a[nef_a.len() - 4..].try_into().expect("4 bytes"));
let checksum_b = u32::from_le_bytes(
nef_b[nef_b.len() - 4..].try_into().expect("4 bytes"));
let hash_a = compute_contract_hash(sender_bytes, checksum_a, "A");
let hash_b = compute_contract_hash(sender_bytes, checksum_b, "B");
prop_assert!(hash_a != hash_b,
"A and B must hash distinctly (names differ → inputs differ); \
got identical hash {:?}", hash_a);
}
#[test]
fn reentrancy_guard_compiles(
_unused in any::<u8>(), ) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract R {
bool private locked;
modifier noReentrant() { require(!locked, "no reentrant"); locked = true; _; locked = false; }
function action() external noReentrant returns (uint256) { return 1; }
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("reentrancy guard compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let artifact = &artifacts[0];
let methods = artifact.manifest["abi"]["methods"].as_array()
.expect("abi.methods must be an array");
let action = methods.iter()
.find(|m| m.get("name").and_then(serde_json::Value::as_str) == Some("action"))
.expect("method `action` must exist in manifest");
prop_assert_eq!(action.get("returntype").and_then(serde_json::Value::as_str),
Some("Integer"),
"action must declare Integer returntype; got {:?}",
action.get("returntype"));
prop_assert_eq!(action.get("offset").and_then(serde_json::Value::as_u64),
Some(0),
"action must live at offset 0 for the execute(&bytecode, &[]) \
pattern to reach it; got {:?}", action.get("offset"));
let result = compile_and_execute(source);
prop_assert!(result.success,
"reentrancy-guarded action must succeed on first call: {:?}",
result.exception.as_ref().map(|e| &e.message));
let got = decode_uint_le(&result.return_data);
prop_assert_eq!(got, num_bigint::BigUint::from(1u8),
"action must return 1 after modifier-guarded path");
}
#[test]
fn large_contract_many_methods_compiles_and_manifest_stable(
n in 20u32..=40u32,
) {
let mut body = String::new();
for i in 0..n {
body.push_str(&format!(
" function m{i}() external pure returns (uint256) {{ return {i}; }}\n",
i = i,
));
}
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract L {{
{body}}}"#, body = body);
let arts_a = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("large-contract compile failed for N={}: {:?}", n, e));
prop_assert!(!arts_a.is_empty());
let art_a = &arts_a[0];
let methods = art_a.manifest["abi"]["methods"].as_array()
.expect("abi.methods must be an array");
prop_assert_eq!(methods.len() as u32, n + 1,
"manifest must have exactly N+1 methods (N getters + _deploy); \
N={}, got {}", n, methods.len());
let method_map: std::collections::HashMap<&str, &serde_json::Value> = methods.iter()
.filter_map(|m| {
let name = m.get("name").and_then(serde_json::Value::as_str)?;
Some((name, m))
})
.collect();
for i in 0..n {
let name = format!("m{}", i);
let m = method_map.get(name.as_str())
.unwrap_or_else(|| panic!("method {} missing from manifest", name));
prop_assert_eq!(m.get("returntype").and_then(serde_json::Value::as_str),
Some("Integer"),
"method {} must declare Integer returntype; got {:?}",
name, m.get("returntype"));
}
prop_assert!(method_map.contains_key("_deploy"),
"manifest must expose the generated `_deploy` method");
let arts_b = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("second compile failed for N={}: {:?}", n, e));
prop_assert!(!arts_b.is_empty());
let art_b = &arts_b[0];
prop_assert_eq!(&art_a.bytecode, &art_b.bytecode,
"same source (N={}) must produce byte-identical bytecode \
across re-compiles", n);
prop_assert_eq!(&art_a.manifest, &art_b.manifest,
"same source (N={}) must produce byte-identical manifest \
across re-compiles", n);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn optimizer_levels_produce_semantically_equivalent_results(
seed in 1u32..=1_000_000u32,
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{
uint256 a = {seed};
return a * 2;
}} }}"#, seed = seed);
let mut results: Vec<(bool, Vec<u8>)> = Vec::new();
for level in 0u8..=2u8 {
let arts = compile_contracts(&source, false, level)
.unwrap_or_else(|e| panic!("opt level {} compile failed: {:?}", level, e));
prop_assert!(!arts.is_empty(), "opt level {} produced no artifacts", level);
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let res = runtime.execute(&arts[0].bytecode, &[])
.expect("execute must not fail at host level");
results.push((res.success, res.return_data));
}
let expected = num_bigint::BigUint::from(seed as u64) * num_bigint::BigUint::from(2u8);
for (level, (success, data)) in results.iter().enumerate() {
prop_assert!(*success, "opt level {} must succeed (seed={})", level, seed);
prop_assert_eq!(decode_uint_le(data), expected.clone(),
"opt level {} diverges semantically for seed={}: return_data={:?}",
level, seed, data);
}
prop_assert_eq!(&results[0].1, &results[1].1, "opt0 vs opt1 return_data divergence");
prop_assert_eq!(&results[1].1, &results[2].1, "opt1 vs opt2 return_data divergence");
}
#[test]
fn view_function_cannot_write_storage_compile_error(
var_name in identifier_strategy(),
) {
prop_assume!(var_name != "bad");
prop_assume!(var_name != "C");
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ uint256 public {v}; function bad() external view returns (uint256) {{ {v} = 7; return {v}; }} }}"#, v = var_name);
let result = compile_contracts(&source, false, 2);
prop_assert!(result.is_err(),
"SECURITY: view-function-writes-storage MUST be a compile error; \
got Ok (silent mutability violation) for var_name={:?}", var_name);
}
#[test]
fn pure_function_cannot_read_state_compile_error(
var_name in identifier_strategy(),
) {
prop_assume!(var_name != "bad");
prop_assume!(var_name != "C");
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ uint256 public {v} = 5; function bad() external pure returns (uint256) {{ return {v}; }} }}"#, v = var_name);
let result = compile_contracts(&source, false, 2);
prop_assert!(result.is_err(),
"SECURITY: pure-function-reads-storage MUST be a compile error; \
got Ok (silent purity violation) for var_name={:?}", var_name);
}
#[test]
fn event_with_indexed_and_dynamic_args_lowers(
_unused in any::<u8>(),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
event Complex(address indexed from, bytes32 indexed topic, uint256 amount, bytes payload);
function go() external { emit Complex(msg.sender, keccak256("TEST"), 42, hex"deadbeef"); }
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("indexed-event compile failed: {:?}", e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let res = runtime.execute(&artifacts[0].bytecode, &[])
.expect("execute of event-emitting contract should not error");
prop_assert!(res.success, "event execution must succeed: {:?}",
res.exception.as_ref().map(|e| &e.message));
prop_assert_eq!(res.logs.len(), 1,
"exactly one LogEntry expected; got {}", res.logs.len());
let log = &res.logs[0];
prop_assert_eq!(log.topics.len(), 3,
"H4: 2 indexed args must surface as topics[1..2] + topic[0] sig; got {} topics",
log.topics.len());
use sha3::{Digest, Keccak256};
let mut hasher = Keccak256::new();
hasher.update(b"Complex(address,bytes32,uint256,bytes)");
let expected_topic0 = hasher.finalize();
prop_assert_eq!(&log.topics[0][..], &expected_topic0[..],
"H4: topics[0] must be keccak256(canonical-sig)");
prop_assert_eq!(log.topics[1].len(), 32,
"H4: topics[1] (msg.sender) must be 32-byte padded; got {} bytes",
log.topics[1].len());
let mut h2 = Keccak256::new();
h2.update(b"TEST");
let expected_topic2 = h2.finalize();
prop_assert_eq!(&log.topics[2][..], &expected_topic2[..],
"H4: topics[2] must be keccak256(\"TEST\") (the bytes32 indexed value)");
prop_assert_eq!(log.data.len(), 128,
"H4: data is the EVM-spec head+tail encoding of (uint256 amount, \
bytes payload); got {} bytes", log.data.len());
let mut expected_amount = [0u8; 32];
expected_amount[31] = 42;
prop_assert_eq!(&log.data[..32], &expected_amount[..],
"H4: data[0..32] must be BE32(42) (the non-indexed amount); got {}",
hex::encode(&log.data[..32]));
let mut expected_off = [0u8; 32];
expected_off[31] = 0x40;
prop_assert_eq!(&log.data[32..64], &expected_off[..],
"H4: data[32..64] must be offset 0x40 for the payload tail; got {}",
hex::encode(&log.data[32..64]));
let mut expected_len = [0u8; 32];
expected_len[31] = 0x04;
prop_assert_eq!(&log.data[64..96], &expected_len[..],
"H4: data[64..96] must be length 4 for hex\"deadbeef\"; got {}",
hex::encode(&log.data[64..96]));
prop_assert_eq!(&log.data[96..100], &[0xde, 0xad, 0xbe, 0xef][..],
"H4: data[96..100] must be 0xdeadbeef left-aligned; got {}",
hex::encode(&log.data[96..128]));
}
#[test]
fn constructor_with_args_compiles_and_deploy_method_reflects_params(
initial in any::<u64>(),
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
uint256 public v = {init};
constructor(uint256 initial) {{ v = initial; }}
function value() external view returns (uint256) {{ return v; }}
}}"#, init = initial);
let arts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("ctor compile failed: {:?}", e));
prop_assert!(!arts.is_empty());
let artifact = &arts[0];
let methods = artifact.manifest["abi"]["methods"].as_array()
.expect("abi.methods must be an array");
let deploy = methods.iter()
.find(|m| m.get("name").and_then(serde_json::Value::as_str) == Some("_deploy"))
.expect("_deploy method must exist in manifest");
let params = deploy["parameters"].as_array()
.expect("_deploy.parameters must be an array");
prop_assert_eq!(params.len(), 2,
"_deploy must accept exactly (data, update); got {} params", params.len());
prop_assert_eq!(params[0]["name"].as_str(), Some("data"),
"_deploy.parameters[0].name must be `data`; got {:?}", params[0]["name"]);
prop_assert_eq!(params[0]["type"].as_str(), Some("Any"),
"_deploy.parameters[0].type must be Any (Neo convention); got {:?}",
params[0]["type"]);
prop_assert_eq!(params[1]["name"].as_str(), Some("update"),
"_deploy.parameters[1].name must be `update`; got {:?}", params[1]["name"]);
prop_assert_eq!(params[1]["type"].as_str(), Some("Boolean"),
"_deploy.parameters[1].type must be Boolean; got {:?}", params[1]["type"]);
let perms = artifact.manifest["permissions"].as_array()
.expect("manifest.permissions must be an array");
let has_stdlib_deserialize = perms.iter().any(|p| {
let methods = match p["methods"].as_array() { Some(a) => a, None => return false };
let method_names: Vec<&str> = methods.iter()
.filter_map(|m| m.as_str()).collect();
method_names.contains(&"jsonDeserialize") && method_names.contains(&"deserialize")
});
prop_assert!(has_stdlib_deserialize,
"parameterised-ctor manifest MUST allow StdLib.jsonDeserialize + \
StdLib.deserialize (per Neo-Express `-d '[7]'` plumbing); \
permissions={:?}", perms);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(15))]
#[test]
fn optimizer_const_folds_add_at_level2(
_unused in any::<u8>(),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) { return 5 + 7; } }"#;
let mut lens: [usize; 3] = [0; 3];
for level in 0u8..=2u8 {
let arts = compile_contracts(source, false, level)
.unwrap_or_else(|e| panic!("opt level {} compile failed: {:?}", level, e));
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let res = runtime.execute(&arts[0].bytecode, &[])
.expect("execute must not fail at host level");
lens[level as usize] = arts[0].bytecode.len();
prop_assert_eq!(observe(&res),
ObservedBehavior::Returned(num_bigint::BigUint::from(12u8)),
"opt level {} must compute 5+7=12; got {:?}", level, res);
}
let actual_shorter = lens[2] < lens[0];
prop_assert!(actual_shorter,
"optimizer did not fold 5+7 — Task #40 confirmed. \
level0.len={} level1.len={} level2.len={}",
lens[0], lens[1], lens[2]);
}
#[test]
fn optimizer_cse_repeated_subexpression(
_unused in any::<u8>(),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) { uint256 a = 17; uint256 b = 29; return (a + b) * (a + b) + (a + b); } }"#;
let mut lens: [usize; 3] = [0; 3];
for level in 0u8..=2u8 {
let arts = compile_contracts(source, false, level)
.unwrap_or_else(|e| panic!("opt level {} compile failed: {:?}", level, e));
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let res = runtime.execute(&arts[0].bytecode, &[])
.expect("execute must not fail at host level");
lens[level as usize] = arts[0].bytecode.len();
prop_assert_eq!(observe(&res),
ObservedBehavior::Returned(num_bigint::BigUint::from(2162u16)),
"opt level {} must compute 46*47=2162; got {:?}", level, res);
}
prop_assert!(lens[2] <= lens[0],
"CSE promotion regressed bytecode size: \
level0.len={} level1.len={} level2.len={}",
lens[0], lens[1], lens[2]);
}
#[test]
fn optimizer_dead_code_elim_unreachable_branch(
_unused in any::<u8>(),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f(uint256 n) external pure returns (uint256) { if (true) return 42; else return n; } }"#;
let mut lens: [usize; 3] = [0; 3];
for level in 0u8..=2u8 {
let arts = compile_contracts(source, false, level)
.unwrap_or_else(|e| panic!("opt level {} compile failed: {:?}", level, e));
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let res = runtime.execute(&arts[0].bytecode, &[])
.expect("execute must not fail at host level");
lens[level as usize] = arts[0].bytecode.len();
prop_assert_eq!(observe(&res),
ObservedBehavior::Returned(num_bigint::BigUint::from(42u8)),
"opt level {} must return 42 from the live `then` branch; got {:?}",
level, res);
}
prop_assert!(lens[2] < lens[0],
"DCE did NOT prune the dead `else` branch: \
level0.len={} level1.len={} level2.len={}",
lens[0], lens[1], lens[2]);
}
#[test]
fn optimizer_does_not_reorder_side_effects(
_unused in any::<u8>(),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
uint256 private log;
event Step(uint256 n);
function f() external returns (uint256) {
emit Step(1);
log = 10;
emit Step(2);
return log;
}
}"#;
use sha3::{Digest, Keccak256};
let mut hasher = Keccak256::new();
hasher.update(b"Step(uint256)");
let expected_sig = hasher.finalize();
for level in [0u8, 2u8] {
let arts = compile_contracts(source, false, level)
.unwrap_or_else(|e| panic!("opt level {} compile failed: {:?}", level, e));
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let res = runtime.execute(&arts[0].bytecode, &[])
.expect("execute must not fail at host level");
prop_assert!(res.success, "opt {} must succeed", level);
prop_assert_eq!(res.logs.len(), 2,
"opt {} must emit exactly 2 Step() events; got {}", level, res.logs.len());
for (idx, log) in res.logs.iter().enumerate() {
prop_assert_eq!(log.topics.len(), 1,
"opt {} log[{}] must have 1 topic (0 indexed args)", level, idx);
prop_assert_eq!(&log.topics[0][..], &expected_sig[..],
"opt {} log[{}].topics[0] must be keccak256(\"Step(uint256)\")",
level, idx);
}
let mut expect_1 = [0u8; 32];
expect_1[31] = 1;
let mut expect_2 = [0u8; 32];
expect_2[31] = 2;
prop_assert_eq!(&res.logs[0].data[..], &expect_1[..],
"opt {} logs[0] must carry Step(1) as BE32(1)", level);
prop_assert_eq!(&res.logs[1].data[..], &expect_2[..],
"opt {} logs[1] must carry Step(2) as BE32(2)", level);
prop_assert_eq!(observe(&res),
ObservedBehavior::Returned(num_bigint::BigUint::from(10u8)),
"opt {} return must be 10 (the written-then-read value)", level);
}
}
#[test]
fn optimizer_preserves_revert_semantics(
_unused in any::<u8>(),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function f() external pure returns (uint256) {
require(false, "fail");
return 99;
}
}"#;
for level in [0u8, 2u8] {
let arts = compile_contracts(source, false, level)
.unwrap_or_else(|e| panic!("opt level {} compile failed: {:?}", level, e));
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let res = runtime.execute(&arts[0].bytecode, &[])
.expect("execute must not fail at host level");
prop_assert!(!res.success,
"opt {} MUST revert (require(false)); optimizer cannot elide revert", level);
let msg = res.exception.as_ref().map(|e| e.message.clone()).unwrap_or_default();
prop_assert!(msg.contains("fail"),
"opt {} revert message must contain the literal 'fail' from require; got {:?}",
level, msg);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn storage_delete_removes_from_iteration(
unique_keys in prop::collection::hash_set(
prop::collection::vec(any::<u8>(), 1..16), 2..12),
raw_delete_count in 1usize..8,
) {
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let account = "0x1234567890123456789012345678901234567890";
let keys: Vec<Vec<u8>> = unique_keys.into_iter().collect();
let n = keys.len();
let delete_count = raw_delete_count.min(n - 1).max(1);
for (i, key) in keys.iter().enumerate() {
let value = (i as u64 + 1).to_le_bytes().to_vec();
runtime.set_storage(account, key, &value).expect("set_storage");
}
for key in keys.iter().take(delete_count) {
runtime.set_storage(account, key, &[]).expect("delete via empty value");
}
let found = runtime.storage_find(account, &[]).expect("storage_find");
let expected_remaining = n - delete_count;
prop_assert_eq!(found.len(), expected_remaining,
"after deleting {}/{} keys, iterator must return {} entries; got {} (entries: {:?})",
delete_count, n, expected_remaining, found.len(), found);
let observed_keys: Vec<&[u8]> = found.iter().map(|(k, _)| k.as_slice()).collect();
let mut sorted = observed_keys.clone();
sorted.sort();
prop_assert_eq!(&observed_keys, &sorted,
"storage_find post-delete must be byte-lex ordered by key");
let deleted_set: std::collections::HashSet<&[u8]> =
keys.iter().take(delete_count).map(|k| k.as_slice()).collect();
for (k, _) in &found {
prop_assert!(!deleted_set.contains(k.as_slice()),
"deleted key {:?} still appears in iteration", k);
}
for (idx, key) in keys.iter().enumerate().skip(delete_count) {
let expected_value = (idx as u64 + 1).to_le_bytes().to_vec();
let got = runtime.get_storage(account, key).expect("get_storage");
prop_assert_eq!(got, Some(expected_value),
"undeleted key {:?} must still round-trip", key);
}
}
#[test]
#[allow(non_snake_case)]
fn abi_encodePacked_matches_reference_concatenation(
a in 0u64..=u64::MAX,
b in 0u64..=u64::MAX,
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (bytes memory) {{
return abi.encodePacked(uint256({a}), uint256({b}));
}} }}"#, a = a, b = b);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("encodePacked compile failed (a={}, b={}): {:?}", a, b, e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifacts[0].bytecode, &[])
.expect("encodePacked execute must not fail at host level");
prop_assert!(result.success,
"encodePacked must succeed; got exception {:?}", result.exception);
let mut expected_be: Vec<u8> = Vec::with_capacity(64);
expected_be.extend_from_slice(&[0u8; 24]); expected_be.extend_from_slice(&a.to_be_bytes());
expected_be.extend_from_slice(&[0u8; 24]);
expected_be.extend_from_slice(&b.to_be_bytes());
let mut expected_le: Vec<u8> = Vec::with_capacity(64);
expected_le.extend_from_slice(&a.to_le_bytes());
expected_le.extend_from_slice(&[0u8; 24]);
expected_le.extend_from_slice(&b.to_le_bytes());
expected_le.extend_from_slice(&[0u8; 24]);
let rd = &result.return_data;
let is_be = rd.as_slice() == expected_be.as_slice();
let is_le = rd.as_slice() == expected_le.as_slice();
prop_assert!(is_be || is_le,
"encodePacked(u256({}), u256({})) did not match BE or LE reference; \
got {} bytes: {:?}\n expected_be={:?}\n expected_le={:?}",
a, b, rd.len(), rd, expected_be, expected_le);
prop_assert!(is_be,
"encodePacked payload is NOT big-endian (EVM-compat). got LE \
layout instead for (a={}, b={}). rd={:?}. This is a \
devpack-compat gap worth filing.",
a, b, rd);
}
#[test]
#[allow(non_snake_case)]
fn abi_encodePacked_small_width_matches_spec(
a in any::<u8>(),
b in any::<u16>(),
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (bytes memory) {{
return abi.encodePacked(uint8({a}), uint16({b}));
}} }}"#, a = a, b = b);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("encodePacked(uint8,uint16) compile failed \
(a={}, b={}): {:?}", a, b, e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifacts[0].bytecode, &[])
.expect("encodePacked execute must not fail at host level");
prop_assert!(result.success,
"encodePacked must succeed; got exception {:?}", result.exception);
let mut expected = Vec::with_capacity(3);
expected.push(a);
expected.extend_from_slice(&b.to_be_bytes());
prop_assert_eq!(result.return_data.len(), 3,
"encodePacked(uint8,uint16) must be 3 bytes (1+2), got {} bytes: {:?}",
result.return_data.len(), result.return_data);
prop_assert_eq!(result.return_data.as_slice(), expected.as_slice(),
"encodePacked(uint8({}), uint16({})) payload mismatch; \
got {:?}, expected {:?}",
a, b, result.return_data, expected);
}
#[test]
fn pragma_solc_v080_vs_v0819_feature_compat(
_unused in any::<u8>(),
) {
let src_v080 = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract C { function f() external pure returns (string memory) { return string.concat("a", "b"); } }"#;
let src_v0819 = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (string memory) { return string.concat("a", "b"); } }"#;
let res_0819 = compile_contracts(src_v0819, false, 2);
prop_assert!(res_0819.is_ok(),
"^0.8.19 MUST compile string.concat (feature available since 0.8.12); got {:?}",
res_0819.err());
let res_080 = compile_contracts(src_v080, false, 2);
prop_assert!(res_080.is_err(),
"^0.8.0 MUST reject string.concat: feature requires pragma >= 0.8.12. \
If this now fires with Ok(_), the feature gate has regressed — see \
`enforce_feature_version_gates` in src/frontend/frontend_parse.rs.");
let err_msg = format!("{:?}", res_080.err());
prop_assert!(err_msg.contains("string.concat") && err_msg.contains("0.8.12"),
"feature-gate diagnostic must name the feature and required version; got {}",
err_msg);
let arts_0819 = res_0819.unwrap();
prop_assert!(!arts_0819.is_empty(),
"^0.8.19 compile must produce at least one artifact");
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&arts_0819[0].bytecode, &[])
.expect("execute must not fail at host level");
prop_assert!(result.success,
"^0.8.19 string.concat execute must succeed; got exception {:?}",
result.exception);
}
#[test]
fn gas_consumption_monotone_with_loop_count(
n1 in 5u32..=10u32,
d12 in 3u32..=8u32,
d23 in 3u32..=8u32,
) {
let n2 = n1 + d12;
let n3 = n2 + d23;
prop_assume!(n3 <= 30);
let make_source = |n: u32| -> String {
format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{
uint256 s = 0;
for (uint256 i = 0; i < {n}; i++) {{ s += i; }}
return s;
}} }}"#, n = n)
};
let run = |n: u32| -> (u64, num_bigint::BigUint) {
let src = make_source(n);
let arts = compile_contracts(&src, false, 2)
.unwrap_or_else(|e| panic!("loop-gas compile N={} failed: {:?}", n, e));
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let res = rt
.execute(&arts[0].bytecode, &[])
.expect("loop-gas execute must not fail at host level");
assert!(res.success, "loop-gas N={} must succeed; exc={:?}", n, res.exception);
(res.gas_used, decode_uint_le(&res.return_data))
};
let (gas1, sum1) = run(n1);
let (gas2, sum2) = run(n2);
let (gas3, sum3) = run(n3);
let expected = |n: u32| -> num_bigint::BigUint {
num_bigint::BigUint::from((n as u64) * ((n as u64).saturating_sub(1)) / 2)
};
prop_assert_eq!(&sum1, &expected(n1),
"loop(N={}) must compute Gauss sum {}; got {}", n1, expected(n1), sum1);
prop_assert_eq!(&sum2, &expected(n2),
"loop(N={}) must compute Gauss sum {}; got {}", n2, expected(n2), sum2);
prop_assert_eq!(&sum3, &expected(n3),
"loop(N={}) must compute Gauss sum {}; got {}", n3, expected(n3), sum3);
prop_assert!(gas1 <= gas2,
"gas_used NOT monotone from N={} ({}) to N={} ({})", n1, gas1, n2, gas2);
prop_assert!(gas2 <= gas3,
"gas_used NOT monotone from N={} ({}) to N={} ({})", n2, gas2, n3, gas3);
prop_assert!(gas1 <= gas3,
"gas_used NOT monotone from N={} ({}) to N={} ({})", n1, gas1, n3, gas3);
}
#[test]
fn cross_contract_call_via_calltoken_or_ignore(
_unused in any::<u8>(),
) {
use neo_devpack_solidity::neo::MethodToken;
let stdlib_hash: [u8; 20] = [
0xc0, 0xef, 0x39, 0xce, 0xe0, 0xe4, 0xe9, 0x25,
0xc6, 0xc2, 0xa0, 0x6a, 0x79, 0xe1, 0x44, 0x0d,
0xd8, 0x6f, 0xce, 0xac,
];
let tokens = vec![MethodToken::new(stdlib_hash, "serialize", 1, true, 0x0F)];
let script: Vec<u8> = vec![0x11, 0x37, 0x00, 0x00, 0x40];
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute_with_tokens(&script, &[], &tokens)
.expect("execute_with_tokens must not fail at host level");
prop_assert!(result.success,
"CALLT → StdLib.serialize must succeed end-to-end; exception={:?}, \
return_data={:?}. If this fires, CALLT dispatch is broken — file \
as a CRITICAL finding and flip to #[ignore].",
result.exception, result.return_data);
prop_assert!(!result.return_data.is_empty(),
"CALLT dispatched to StdLib.serialize but return_data is empty — \
native returned Null, suggesting the method lookup missed or the \
params array wasn't received. Full result: {:?}", result);
prop_assert_eq!(
&result.return_data[..],
&[0x02u8, 1, 0, 0, 0, 0, 0, 0, 0][..],
"StdLib.serialize(Integer 1) must emit Neo binary [0x02, LE i64], \
not JSON. If this regresses to JSON, the S1 fix was reverted. \
Got: {:?}",
std::str::from_utf8(&result.return_data).ok()
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn abi_encode_nonpacked_returns_bytes(
a in 0u64..=u64::MAX,
b in 0u64..=u64::MAX,
) {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (bytes memory) {{
return abi.encode(uint256({a}), uint256({b}));
}} }}"#, a = a, b = b);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("abi.encode compile failed (a={}, b={}): {:?}", a, b, e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifacts[0].bytecode, &[])
.expect("abi.encode execute must not fail at host level");
prop_assert!(result.success,
"abi.encode must succeed; got exception {:?}", result.exception);
let mut expected_be: Vec<u8> = Vec::with_capacity(64);
expected_be.extend_from_slice(&[0u8; 24]);
expected_be.extend_from_slice(&a.to_be_bytes());
expected_be.extend_from_slice(&[0u8; 24]);
expected_be.extend_from_slice(&b.to_be_bytes());
let rd = &result.return_data;
let as_str = std::str::from_utf8(rd).ok();
let is_json_array = as_str
.map(|s| s.starts_with(r#"{"type":"Array""#))
.unwrap_or(false);
let is_correct_be = rd.as_slice() == expected_be.as_slice();
let is_length_prefixed = {
let mut len_prefix = [0u8; 32];
len_prefix[24..].copy_from_slice(&64u64.to_be_bytes());
rd.len() == 96
&& rd[0..32] == len_prefix[..]
&& rd[32..96] == expected_be[..]
};
prop_assert!(is_json_array || is_correct_be || is_length_prefixed,
"abi.encode(u256({}), u256({})) return_data has UNKNOWN shape — \
not JSON-array (legacy bug), not canonical BE (post-fix), not \
length-prefixed ABI. rd.len={}, rd={:?}, utf8={:?}",
a, b, rd.len(), rd, as_str);
prop_assert!(is_correct_be,
"abi.encode(u256({}), u256({})) must produce EVM-canonical BE \
64 bytes. rd.len={}, rd={:?}. If is_json_array={}, Task #44 \
regressed — re-check the runtime `abiEncode` handler.",
a, b, rd.len(), rd, is_json_array);
}
#[test]
fn keccak256_bytes_literal_matches_reference(
data in prop::collection::vec(any::<u8>(), 0..=64),
) {
use sha3::{Digest, Keccak256};
let hex_str = hex::encode(&data);
prop_assert_eq!(hex_str.len() % 2, 0,
"hex::encode must produce even-length output");
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (bytes32) {{
return keccak256(hex"{hex}");
}} }}"#, hex = hex_str);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("keccak256 compile failed (hex={:?}): {:?}", hex_str, e));
prop_assert!(!artifacts.is_empty());
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&artifacts[0].bytecode, &[])
.expect("keccak256 execute must not fail at host level");
prop_assert!(result.success,
"keccak256(hex{:?}) must succeed; got exception {:?}",
hex_str, result.exception);
let expected = Keccak256::digest(&data).to_vec();
prop_assert_eq!(result.return_data.len(), 32,
"keccak256 must return exactly 32 bytes; got {}", result.return_data.len());
prop_assert_eq!(&result.return_data, &expected,
"keccak256(hex{:?}) = {:?}, expected {:?} — keccak over direct byte \
literal IS correct; any abi.encode*-keccak bridge bug is in the \
encode path, not the hash path.",
hex_str, result.return_data, expected);
}
#[test]
fn array_push_pop_length_compile_and_execute(
n in 1u32..=10u32,
idx_seed in 0u32..10,
) {
use num_bigint::BigUint;
let idx = idx_seed % n;
let src_len = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{
uint256[] memory a = new uint256[]({n});
a[0] = 10; a[{last}] = 30;
return a.length;
}} }}"#, n = n, last = n.saturating_sub(1));
let arts_len = compile_contracts(&src_len, false, 2)
.unwrap_or_else(|e| panic!("array.length compile (n={}) failed: {:?}", n, e));
prop_assert!(!arts_len.is_empty());
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let r_len = rt.execute(&arts_len[0].bytecode, &[])
.expect("array.length execute must not fail at host level");
prop_assert!(r_len.success,
"array.length (n={}) must succeed; exc={:?}", n, r_len.exception);
let got_len = decode_uint_le(&r_len.return_data);
prop_assert_eq!(&got_len, &BigUint::from(n as u64),
"new uint256[]({}).length must equal {}; got {} (rd={:?})",
n, n, got_len, r_len.return_data);
let v: u64 = 42u64 + (idx as u64) * 3;
let src_idx = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{
uint256[] memory b = new uint256[]({n});
b[{idx}] = {v};
return b[{idx}];
}} }}"#, n = n, idx = idx, v = v);
let arts_idx = compile_contracts(&src_idx, false, 2)
.unwrap_or_else(|e| panic!("array[idx] compile (n={}, idx={}) failed: {:?}", n, idx, e));
prop_assert!(!arts_idx.is_empty());
let mut rt2 = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let r_idx = rt2.execute(&arts_idx[0].bytecode, &[])
.expect("array[idx] execute must not fail at host level");
prop_assert!(r_idx.success,
"array[idx] (n={}, idx={}, v={}) must succeed; exc={:?}",
n, idx, v, r_idx.exception);
let got_v = decode_uint_le(&r_idx.return_data);
prop_assert_eq!(&got_v, &BigUint::from(v),
"b[{}]={} then return b[{}] (in a [{}]-array) must yield {}; got {} (rd={:?})",
idx, v, idx, n, v, got_v, r_idx.return_data);
}
#[test]
fn inheritance_storage_slots_isolated(
_seed in any::<u8>(),
) {
let source = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract A { uint256 private a1 = 100; uint256 public a1Read = 100; }
contract B is A { uint256 private b1 = 200; uint256 public b1Read = 200; }
contract C is B {
function readBoth() external view returns (uint256, uint256) { return (a1Read, b1Read); }
}"#;
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("inheritance compile failed: {:?}", e));
prop_assert!(artifacts.len() >= 3,
"inheritance compile should produce 3 artifacts (A, B, C); got {}",
artifacts.len());
let c_art = artifacts
.iter()
.find(|a| a.metadata.name == "C")
.expect("artifact named C must exist");
let mut runtime = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = runtime
.execute(&c_art.bytecode, &[])
.expect("inheritance execute must not fail at host level");
prop_assert!(result.success,
"readBoth must succeed end-to-end; exc={:?}", result.exception);
let rd = &result.return_data;
prop_assert_eq!(rd.len(), 64,
"inheritance readBoth post-Task-#64 must be 2 * 32 = 64 bytes; \
got rd.len={}, rd={:?}", rd.len(), rd);
let expected_zero = [0u8; 64];
prop_assert_eq!(rd.as_slice(), &expected_zero[..],
"storage initializers are NOT running at execute-time (readBoth \
returns 0 for both slots). If this ever flips to 100/200, \
congratulations — the deploy path now runs. rd={:?}", rd);
}
#[test]
fn string_length_ascii_vs_multibyte(
_seed in any::<u8>(),
) {
use num_bigint::BigUint;
let src_ascii = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function ascii() external pure returns (uint256) {
return bytes("Hello").length;
} }"#;
let arts_a = compile_contracts(src_ascii, false, 2)
.unwrap_or_else(|e| panic!("ascii compile failed: {:?}", e));
prop_assert!(!arts_a.is_empty());
let mut rt_a = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let r_a = rt_a.execute(&arts_a[0].bytecode, &[])
.expect("ascii execute must not fail at host level");
prop_assert!(r_a.success,
"ascii() must succeed; exc={:?}", r_a.exception);
let got_a = decode_uint_le(&r_a.return_data);
prop_assert_eq!(&got_a, &BigUint::from(5u8),
"bytes(\"Hello\").length must be 5 (UTF-8 bytes); got {} (rd={:?})",
got_a, r_a.return_data);
let src_multi = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function multi() external pure returns (uint256) {
return bytes(unicode"Helloé").length;
} }"#;
let arts_m = compile_contracts(src_multi, false, 2)
.unwrap_or_else(|e| panic!("multi-byte compile failed: {:?}", e));
prop_assert!(!arts_m.is_empty());
let mut rt_m = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let r_m = rt_m.execute(&arts_m[0].bytecode, &[])
.expect("multi-byte execute must not fail at host level");
prop_assert!(r_m.success,
"multi() must succeed; exc={:?}", r_m.exception);
let got_m = decode_uint_le(&r_m.return_data);
prop_assert_eq!(&got_m, &BigUint::from(7u8),
"bytes(unicode\"Helloé\").length must be 7 (UTF-8 bytes: 5 ASCII + \
2 for é); got {} (rd={:?}). If this ever returns 6, the compiler \
is counting codepoints instead of UTF-8 bytes — that's a spec \
violation worth filing.",
got_m, r_m.return_data);
prop_assert!(got_m > got_a,
"UTF-8 byte-length semantics require ascii.len={} < multi.len={}; \
if equal (6 == 5? no; both 6?), the compiler is codepoint-counting.",
got_a, got_m);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(20))]
#[test]
fn callt_stdlib_itoa_roundtrip_via_token(
n in 0u32..=999_999_999u32,
) {
use neo_devpack_solidity::neo::MethodToken;
let stdlib_hash: [u8; 20] = [
0xc0, 0xef, 0x39, 0xce, 0xe0, 0xe4, 0xe9, 0x25,
0xc6, 0xc2, 0xa0, 0x6a, 0x79, 0xe1, 0x44, 0x0d,
0xd8, 0x6f, 0xce, 0xac,
];
let tokens_itoa = vec![MethodToken::new(stdlib_hash, "itoa", 1, true, 0x0F)];
let mut script_itoa: Vec<u8> = vec![0x02];
script_itoa.extend_from_slice(&(n as i32).to_le_bytes());
script_itoa.extend_from_slice(&[0x37, 0x00, 0x00, 0x40]);
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result_itoa = rt.execute_with_tokens(&script_itoa, &[], &tokens_itoa)
.expect("itoa execute_with_tokens must not fail at host level");
prop_assert!(result_itoa.success,
"itoa(N={}) CALLT must succeed end-to-end; exc={:?}",
n, result_itoa.exception);
let expected_itoa = n.to_string();
prop_assert_eq!(&result_itoa.return_data, &expected_itoa.as_bytes().to_vec(),
"itoa({}) must return '{}' as UTF-8 bytes; got {:?} (utf8={:?}). \
If this fires with rd=[], StdLib.itoa is NOT implemented — see \
#[ignore] reason. If rd is non-empty but wrong, there's a \
separate encoding bug.",
n, expected_itoa, result_itoa.return_data,
std::str::from_utf8(&result_itoa.return_data).ok());
let tokens_atoi = vec![MethodToken::new(stdlib_hash, "atoi", 1, true, 0x0F)];
let s = expected_itoa.as_bytes();
prop_assert!(s.len() <= 255,
"atoi input too long for PUSHDATA1 encoding (max 255); N was {}", n);
let mut script_atoi: Vec<u8> = vec![0x0C, s.len() as u8];
script_atoi.extend_from_slice(s);
script_atoi.extend_from_slice(&[0x37, 0x00, 0x00, 0x40]);
let mut rt2 = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result_atoi = rt2.execute_with_tokens(&script_atoi, &[], &tokens_atoi)
.expect("atoi execute_with_tokens must not fail at host level");
prop_assert!(result_atoi.success,
"atoi('{}') CALLT must succeed; exc={:?}",
expected_itoa, result_atoi.exception);
let got_n = decode_uint_le(&result_atoi.return_data);
prop_assert_eq!(&got_n, &num_bigint::BigUint::from(n),
"atoi(itoa({})) must equal {}; got {} (rd={:?})",
n, n, got_n, result_atoi.return_data);
}
#[test]
fn callt_cryptolib_sha256_matches_sha2_crate(
data in prop::collection::vec(any::<u8>(), 0..=32),
) {
use neo_devpack_solidity::neo::MethodToken;
use sha2::{Digest, Sha256};
let cryptolib_hash: [u8; 20] = [
0x1b, 0xf5, 0x75, 0xab, 0x11, 0x89, 0x68, 0x84,
0x13, 0x61, 0x0a, 0x35, 0xa1, 0x28, 0x86, 0xcd,
0xe0, 0xb6, 0x6c, 0x72,
];
let tokens = vec![MethodToken::new(cryptolib_hash, "sha256", 1, true, 0x0F)];
prop_assert!(data.len() <= 255, "PUSHDATA1 length must fit in u8");
let mut script: Vec<u8> = vec![0x0C, data.len() as u8];
script.extend_from_slice(&data);
script.extend_from_slice(&[0x37, 0x00, 0x00, 0x40]);
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = rt.execute_with_tokens(&script, &[], &tokens)
.expect("sha256 execute_with_tokens must not fail at host level");
prop_assert!(result.success,
"CALLT → CryptoLib.sha256 (data.len={}) must succeed; exc={:?}",
data.len(), result.exception);
let expected = Sha256::digest(&data).to_vec();
prop_assert_eq!(result.return_data.len(), 32,
"sha256 must return 32 bytes; got {} for data.len={}",
result.return_data.len(), data.len());
prop_assert_eq!(&result.return_data, &expected,
"sha256(data.len={}) digest mismatch; got {:?}, expected {:?}. \
If this fires, either CALLT dispatch is broken (see batch #15 \
harness #5) or CryptoLib.sha256 isn't wired to the sha2 crate \
(see src/runtime/execution/execution_impl_part2_native/crypto.rs).",
data.len(), result.return_data, expected);
}
#[test]
fn bitwise_and_or_xor_shl_shr_not_single_fn(
a in 0i64..=i64::MAX,
b in 0i64..=i64::MAX,
s in 0u32..=63u32,
) {
use num_bigint::BigUint;
let au = a as u64;
let bu = b as u64;
let run_expr = |expr: &str, label: &str| -> BigUint {
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{ return {expr}; }} }}"#);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("{} compile failed: {:?}", label, e));
assert!(!artifacts.is_empty(), "{}: no artifacts", label);
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = rt.execute(&artifacts[0].bytecode, &[])
.unwrap_or_else(|e| panic!("{} host err: {:?}", label, e));
assert!(result.success, "{} must succeed; exc={:?}", label, result.exception);
decode_uint_le(&result.return_data)
};
let got_and = run_expr(
&format!("uint256({au}) & uint256({bu})"),
"bitwise_and",
);
prop_assert_eq!(&got_and, &BigUint::from(au & bu),
"uint256({}) & uint256({}) must equal {} (u64); got {}",
au, bu, au & bu, got_and);
let got_or = run_expr(
&format!("uint256({au}) | uint256({bu})"),
"bitwise_or",
);
prop_assert_eq!(&got_or, &BigUint::from(au | bu),
"uint256({}) | uint256({}) must equal {} (u64); got {}",
au, bu, au | bu, got_or);
let got_xor = run_expr(
&format!("uint256({au}) ^ uint256({bu})"),
"bitwise_xor",
);
prop_assert_eq!(&got_xor, &BigUint::from(au ^ bu),
"uint256({}) ^ uint256({}) must equal {} (u64); got {}",
au, bu, au ^ bu, got_xor);
let got_shl = run_expr(
&format!("uint256(1) << uint256({s})"),
"bitwise_shl",
);
prop_assert_eq!(&got_shl, &BigUint::from(1u64 << s),
"uint256(1) << uint256({}) must equal {}; got {}. \
SHL silently returns 0 for s ≥ 64 per bitwise.rs:96-103.",
s, 1u64 << s, got_shl);
let lhs = i64::MAX as u64;
let got_shr = run_expr(
&format!("uint256({lhs}) >> uint256({s})"),
"bitwise_shr",
);
prop_assert_eq!(&got_shr, &BigUint::from(lhs >> s),
"uint256(i64::MAX) >> uint256({}) must equal {}; got {}",
s, lhs >> s, got_shr);
let got_not = run_expr(
&format!("~uint256({au})"),
"bitwise_not",
);
let u256_max = (BigUint::from(1u8) << 256u32) - BigUint::from(1u8);
let expected_not = &u256_max - BigUint::from(au);
prop_assert_eq!(&got_not, &expected_not,
"~uint256({}) must equal (2^256-1)-au; got {}", au, got_not);
}
#[test]
fn static_array_index_read_write(
idx in 0u32..=4u32,
v in any::<u64>(),
) {
use num_bigint::BigUint;
let source_idx = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{ function f() external pure returns (uint256) {{
uint256[5] memory a;
a[{idx}] = {v};
return a[{idx}];
}} }}"#, idx = idx, v = v);
let artifacts = compile_contracts(&source_idx, false, 2)
.unwrap_or_else(|e| panic!("static_array_idx compile failed (idx={}, v={}): {:?}", idx, v, e));
prop_assert!(!artifacts.is_empty());
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = rt.execute(&artifacts[0].bytecode, &[])
.expect("static_array_idx execute must not fail at host level");
prop_assert!(result.success,
"static array idx={} v={} round-trip must succeed; exc={:?}. \
If this fires with 'SETITEM: unsupported target Integer(0)', \
static arrays are still broken — see #[ignore] reason.",
idx, v, result.exception);
let got_v = decode_uint_le(&result.return_data);
prop_assert_eq!(&got_v, &BigUint::from(v),
"a[{}]={}; return a[{}] must yield {}; got {}", idx, v, idx, v, got_v);
let source_len = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C { function f() external pure returns (uint256) {
uint256[5] memory a;
return a.length;
} }"#;
let arts_len = compile_contracts(source_len, false, 2)
.unwrap_or_else(|e| panic!("static_array_len compile failed: {:?}", e));
prop_assert!(!arts_len.is_empty());
let mut rt2 = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result_len = rt2.execute(&arts_len[0].bytecode, &[])
.expect("static_array_len execute must not fail at host level");
prop_assert!(result_len.success,
"static array .length must succeed; exc={:?}. If this fires \
with 'SIZE: unsupported type', static arrays are still broken.",
result_len.exception);
let got_len = decode_uint_le(&result_len.return_data);
prop_assert_eq!(&got_len, &BigUint::from(5u32),
"uint256[5] memory a; a.length must == 5 (Solidity spec); got {}",
got_len);
}
#[test]
fn struct_value_compile_and_return_first_field(
a in any::<u64>(),
b in any::<u64>(),
) {
use num_bigint::BigUint;
let source = format!(r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {{
struct Point {{ uint256 x; uint256 y; }}
function f() external pure returns (uint256) {{
Point memory p = Point({{x: {a}, y: {b}}});
return p.x;
}}
}}"#, a = a, b = b);
let artifacts = compile_contracts(&source, false, 2)
.unwrap_or_else(|e| panic!("struct compile failed (a={}, b={}): {:?}", a, b, e));
prop_assert!(!artifacts.is_empty(),
"struct compile must produce at least one artifact");
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = rt.execute(&artifacts[0].bytecode, &[])
.expect("struct execute must not fail at host level");
prop_assert!(result.success,
"struct (a={}, b={}) must succeed; exc={:?}",
a, b, result.exception);
let got_x = decode_uint_le(&result.return_data);
prop_assert_eq!(&got_x, &BigUint::from(a),
"Point({{x: {}, y: {}}}); return p.x must == {}; got {} (rd={:?}). \
If this fires with 0 and b != 0, there's a field-order mix-up. \
If return_data starts with {{\"type\":\"Array\", ...}} (JSON), \
the lowering now returns the whole struct instead of p.x — \
that would be a NEW bug worth filing.",
a, b, a, got_x, result.return_data);
let utf8 = std::str::from_utf8(&result.return_data).ok();
let looks_like_json = utf8
.map(|s| s.starts_with(r#"{"type":"#))
.unwrap_or(false);
prop_assert!(!looks_like_json,
"struct p.x return should be 8-16 LE scalar bytes, NOT JSON-serialized \
StackItem. If this fires, the lowering regressed to whole-struct return. \
rd={:?}, utf8={:?}", result.return_data, utf8);
}
}