#![allow(clippy::uninlined_format_args)]
#![allow(clippy::needless_borrows_for_generic_args)]
use neo_devpack_solidity::neo::MethodToken;
use neo_devpack_solidity::runtime::{NeoRuntime, RuntimeConfig};
use proptest::prelude::*;
const STDLIB_HASH: [u8; 20] = [
0xc0, 0xef, 0x39, 0xce, 0xe0, 0xe4, 0xe9, 0x25, 0xc6, 0xc2, 0xa0, 0x6a, 0x79, 0xe1, 0x44, 0x0d,
0xd8, 0x6f, 0xce, 0xac,
];
fn build_callt_script(args: &[Vec<u8>]) -> Vec<u8> {
let mut script = Vec::with_capacity(8 + args.iter().map(|a| 2 + a.len()).sum::<usize>());
for arg in args {
debug_assert!(arg.len() <= 255, "PUSHDATA1 max length is 255");
script.push(0x0C); script.push(arg.len() as u8);
script.extend_from_slice(arg);
}
script.extend_from_slice(&[0x37, 0x00, 0x00, 0x40]);
script
}
fn call_stdlib(method: &str, args: &[Vec<u8>]) -> (bool, Vec<u8>, Option<String>) {
let tokens = vec![MethodToken::new(
STDLIB_HASH,
method,
args.len() as u16,
true,
0x0F,
)];
let script = build_callt_script(args);
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("runtime");
let result = rt
.execute_with_tokens(&script, &[], &tokens)
.expect("execute_with_tokens must not fail at host level");
(
result.success,
result.return_data,
result.exception.map(|e| e.message),
)
}
fn push_i64_le(v: i64) -> Vec<u8> {
v.to_le_bytes().to_vec()
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn stdlib_itoa_atoi_decimal_roundtrip(
n in i32::MIN..=i32::MAX,
) {
let (ok_i, rd_i, exc_i) = call_stdlib(
"itoa",
&[push_i64_le(n as i64), push_i64_le(10)],
);
prop_assert!(ok_i, "itoa({}, 10) must succeed; exc={:?}", n, exc_i);
let s = std::str::from_utf8(&rd_i)
.map(|s| s.to_string())
.unwrap_or_default();
prop_assert_eq!(&s, &n.to_string(),
"itoa({}, 10) must equal format!(\"{{}}\"); got {:?}", n, rd_i);
let (ok_a, rd_a, exc_a) = call_stdlib(
"atoi",
&[s.as_bytes().to_vec(), push_i64_le(10)],
);
prop_assert!(ok_a, "atoi({:?}, 10) must succeed; exc={:?}", s, exc_a);
let mut buf = [0u8; 8];
let copy_len = rd_a.len().min(8);
buf[..copy_len].copy_from_slice(&rd_a[..copy_len]);
let got = i64::from_le_bytes(buf);
prop_assert_eq!(got, n as i64,
"atoi(itoa({})) must round-trip; got {} (rd={:?})", n, got, rd_a);
}
#[test]
fn stdlib_itoa_atoi_hex_roundtrip(
n in i32::MIN..=i32::MAX,
) {
let (ok_i, rd_i, exc_i) = call_stdlib(
"itoa",
&[push_i64_le(n as i64), push_i64_le(16)],
);
prop_assert!(ok_i, "itoa({}, 16) must succeed; exc={:?}", n, exc_i);
let s = std::str::from_utf8(&rd_i).map(|s| s.to_string()).unwrap_or_default();
let expected = if n < 0 {
format!("-{:X}", (n as i64).unsigned_abs())
} else {
format!("{:X}", n as u64)
};
prop_assert_eq!(&s, &expected,
"itoa({}, 16) must match StdLib hex shape; got {:?}", n, rd_i);
let (ok_a, rd_a, exc_a) = call_stdlib(
"atoi",
&[s.as_bytes().to_vec(), push_i64_le(16)],
);
prop_assert!(ok_a, "atoi({:?}, 16) must succeed; exc={:?}", s, exc_a);
let mut buf = [0u8; 8];
let copy_len = rd_a.len().min(8);
buf[..copy_len].copy_from_slice(&rd_a[..copy_len]);
let got = i64::from_le_bytes(buf);
prop_assert_eq!(got, n as i64,
"atoi(itoa({}, 16), 16) must round-trip; got {} (rd={:?})", n, got, rd_a);
}
#[test]
fn stdlib_itoa_decimal_matches_format(
n in any::<i32>(),
) {
let (ok, rd, exc) = call_stdlib("itoa", &[push_i64_le(n as i64), push_i64_le(10)]);
prop_assert!(ok, "itoa({}, 10) must succeed; exc={:?}", n, exc);
let got = std::str::from_utf8(&rd).map(|s| s.to_string()).unwrap_or_default();
prop_assert_eq!(&got, &format!("{}", n),
"itoa({}, 10) must equal format!(\"{{}}\"); got {:?}", n, rd);
}
#[test]
fn stdlib_atoi_decimal_matches_parse(
n in i32::MIN..=i32::MAX,
) {
let s = n.to_string();
let (ok, rd, exc) = call_stdlib(
"atoi",
&[s.as_bytes().to_vec(), push_i64_le(10)],
);
prop_assert!(ok, "atoi({:?}, 10) must succeed; exc={:?}", s, exc);
let mut buf = [0u8; 8];
let copy_len = rd.len().min(8);
buf[..copy_len].copy_from_slice(&rd[..copy_len]);
let got = i64::from_le_bytes(buf);
let expected: i64 = s.parse().expect("decimal n.to_string() round-trips");
prop_assert_eq!(got, expected,
"atoi({:?}) must equal s.parse::<i64>(); got {} (rd={:?})",
s, got, rd);
}
#[test]
fn stdlib_atoi_malformed_returns_zero(
garbage in prop_oneof![
Just("".to_string()),
Just("xyz".to_string()),
Just("12abc".to_string()),
Just("0xff".to_string()), Just(" \t ".to_string()), ],
) {
let (ok, rd, exc) = call_stdlib(
"atoi",
&[garbage.as_bytes().to_vec(), push_i64_le(10)],
);
prop_assert!(ok,
"atoi({:?}, 10) on malformed input must NOT fault; exc={:?}",
garbage, exc);
let mut buf = [0u8; 8];
let copy_len = rd.len().min(8);
buf[..copy_len].copy_from_slice(&rd[..copy_len]);
let got = i64::from_le_bytes(buf);
prop_assert_eq!(got, 0,
"atoi({:?}) malformed input must return 0; got {} (rd={:?})",
garbage, got, rd);
}
#[test]
fn stdlib_atoi_double_negative_returns_zero(
_seed in any::<u8>(),
) {
let (ok, rd, exc) = call_stdlib(
"atoi",
&[b"--42".to_vec(), push_i64_le(10)],
);
prop_assert!(ok, "atoi must not fault on `--42`; exc={:?}", exc);
let mut buf = [0u8; 8];
let copy_len = rd.len().min(8);
buf[..copy_len].copy_from_slice(&rd[..copy_len]);
let got = i64::from_le_bytes(buf);
prop_assert_eq!(got, 0,
"atoi(\"--42\") regression: bug #21 fix slipped — body now \
accepts a sign char again. Expected 0, got {} (rd={:?})",
got, rd);
}
#[test]
fn stdlib_itoa_unsupported_base_falls_back_to_decimal(
n in i32::MIN..=i32::MAX,
base in prop_oneof![Just(2i64), Just(8i64), Just(36i64), Just(0i64), Just(-1i64)],
) {
let (ok, rd, exc) = call_stdlib(
"itoa",
&[push_i64_le(n as i64), push_i64_le(base)],
);
prop_assert!(ok, "itoa({}, {}) must succeed; exc={:?}", n, base, exc);
let got = std::str::from_utf8(&rd).map(|s| s.to_string()).unwrap_or_default();
prop_assert_eq!(&got, &n.to_string(),
"itoa({}, base={}) must fall back to decimal; got {:?}",
n, base, rd);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn stdlib_base64_roundtrip(
bytes in prop::collection::vec(any::<u8>(), 0..=180),
) {
let (ok_e, rd_e, exc_e) = call_stdlib("base64encode", std::slice::from_ref(&bytes));
prop_assert!(ok_e, "base64Encode(len={}) must succeed; exc={:?}",
bytes.len(), exc_e);
prop_assert!(rd_e.len() <= 255,
"base64 encoded length {} exceeds PUSHDATA1 max", rd_e.len());
let (ok_d, rd_d, exc_d) = call_stdlib("base64decode", std::slice::from_ref(&rd_e));
prop_assert!(ok_d, "base64Decode(...) must succeed; exc={:?}", exc_d);
prop_assert_eq!(&rd_d, &bytes,
"base64Decode(base64Encode(b)) != b; encoded={:?}, got={:?}, want={:?}",
std::str::from_utf8(&rd_e).ok(), rd_d, bytes);
}
#[test]
fn stdlib_base64_encode_matches_base64_crate(
bytes in prop::collection::vec(any::<u8>(), 0..=200),
) {
use base64::Engine;
let (ok, rd, exc) = call_stdlib("base64encode", std::slice::from_ref(&bytes));
prop_assert!(ok, "base64Encode must succeed; exc={:?}", exc);
let got = std::str::from_utf8(&rd).map(|s| s.to_string()).unwrap_or_default();
let expected = base64::engine::general_purpose::STANDARD.encode(&bytes);
prop_assert_eq!(&got, &expected,
"base64Encode disagrees with base64 crate STANDARD; \
input.len={}, got={:?}, expected={:?}",
bytes.len(), got, expected);
}
#[test]
fn stdlib_base64_decode_matches_base64_crate(
bytes in prop::collection::vec(any::<u8>(), 0..=200),
) {
use base64::Engine;
let encoded = base64::engine::general_purpose::STANDARD.encode(&bytes);
prop_assume!(encoded.len() <= 255);
let (ok, rd, exc) = call_stdlib("base64decode", &[encoded.as_bytes().to_vec()]);
prop_assert!(ok, "base64Decode must succeed; exc={:?}", exc);
prop_assert_eq!(&rd, &bytes,
"base64Decode of base64-crate-encoded bytes != original; \
encoded={:?}, got={:?}, want={:?}", encoded, rd, bytes);
}
#[test]
fn stdlib_base64_malformed_returns_empty(
garbage in prop_oneof![
Just("???".to_string()), Just("AAA".to_string()), Just("====".to_string()), Just("AAAA===AAAA".to_string()), ],
) {
let (ok, rd, exc) = call_stdlib("base64decode", &[garbage.as_bytes().to_vec()]);
prop_assert!(ok,
"base64Decode malformed input must NOT fault; input={:?}, exc={:?}",
garbage, exc);
prop_assert!(rd.is_empty(),
"base64Decode of malformed input must return empty; \
input={:?}, got={:?}", garbage, rd);
}
#[test]
fn stdlib_base64_mid_padding_returns_empty(
_seed in any::<u8>(),
) {
let (ok, rd, exc) = call_stdlib("base64decode", &[b"AB=C".to_vec()]);
prop_assert!(ok, "base64Decode must not fault on `AB=C`; exc={:?}", exc);
prop_assert!(rd.is_empty(),
"base64Decode(\"AB=C\") regression: bug #22 fix slipped — runtime \
now decodes the mid-padding chunk to non-empty bytes. Expected \
[], got {:?}", rd);
use base64::Engine;
let oracle = base64::engine::general_purpose::STANDARD.decode("AB=C");
prop_assert!(oracle.is_err(),
"base64 crate must reject `AB=C`; got Ok({:?})", oracle.ok());
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn stdlib_base58_encode_currently_returns_empty_no_panic(
bytes in prop::collection::vec(any::<u8>(), 0..=64),
) {
let (ok, rd, exc) = call_stdlib("base58encode", std::slice::from_ref(&bytes));
prop_assert!(ok,
"base58Encode(len={}) must NOT fault even when unimplemented; exc={:?}",
bytes.len(), exc);
let _staged_expected = bs58::encode(&bytes).into_string();
prop_assert!(rd.is_empty(),
"base58Encode is currently unimplemented (stdlib.rs::invoke_native_stdlib \
default arm). Contract: returns empty Null. If this fires with a \
non-empty rd, the native landed — flip the assertion to the staged \
differential. rd={:?}, expected-when-implemented={:?}",
rd, _staged_expected);
}
#[test]
fn stdlib_base58_decode_currently_returns_empty_no_panic(
bytes in prop::collection::vec(any::<u8>(), 0..=64),
) {
let encoded = bs58::encode(&bytes).into_string();
prop_assume!(encoded.len() <= 255);
let (ok, rd, exc) = call_stdlib("base58decode", &[encoded.as_bytes().to_vec()]);
prop_assert!(ok,
"base58Decode must NOT fault even when unimplemented; exc={:?}", exc);
prop_assert!(rd.is_empty(),
"base58Decode is currently unimplemented; got non-empty rd={:?} \
— flip to differential: assert_eq!(rd, original_bytes={:?})",
rd, bytes);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(24))]
#[test]
fn stdlib_memorycompare_currently_returns_empty_no_panic(
left in prop::collection::vec(any::<u8>(), 0..=64),
right in prop::collection::vec(any::<u8>(), 0..=64),
) {
let (ok, rd, exc) = call_stdlib("memorycompare", &[left.clone(), right.clone()]);
prop_assert!(ok,
"memoryCompare must NOT fault even when unimplemented; exc={:?}", exc);
let _staged: i32 = match left.cmp(&right) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
};
prop_assert!(rd.is_empty(),
"memoryCompare is unimplemented; got rd={:?} (staged expected={})",
rd, _staged);
}
#[test]
fn stdlib_memorysearch_currently_returns_empty_no_panic(
haystack in prop::collection::vec(any::<u8>(), 0..=64),
needle in prop::collection::vec(any::<u8>(), 0..=8),
) {
let (ok, rd, exc) = call_stdlib("memorysearch", &[haystack.clone(), needle.clone()]);
prop_assert!(ok,
"memorySearch must NOT fault even when unimplemented; exc={:?}", exc);
let _staged: i64 = if needle.is_empty() {
0
} else {
haystack.windows(needle.len())
.position(|w| w == needle.as_slice())
.map(|i| i as i64)
.unwrap_or(-1)
};
prop_assert!(rd.is_empty(),
"memorySearch is unimplemented; got rd={:?} (staged expected={})",
rd, _staged);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(48))]
#[test]
fn stdlib_serialize_deserialize_bytearray_roundtrip(
bytes in prop::collection::vec(any::<u8>(), 0..=128),
) {
let (ok_s, rd_s, exc_s) = call_stdlib("serialize", std::slice::from_ref(&bytes));
prop_assert!(ok_s, "serialize must succeed; exc={:?}", exc_s);
prop_assert!(!rd_s.is_empty(),
"serialize of ByteArray must produce non-empty binary; bytes.len={}",
bytes.len());
prop_assert_eq!(
rd_s.first(), Some(&0x00),
"serialize(ByteArray) must start with the 0x00 ByteArray tag; got {:?}",
rd_s
);
prop_assert!(
!rd_s.starts_with(b"{") && !rd_s.starts_with(b"["),
"serialize must NOT emit JSON (S1 regression); got {:?}",
std::str::from_utf8(&rd_s).ok()
);
prop_assume!(rd_s.len() <= 255);
let (ok_d, rd_d, exc_d) = call_stdlib("deserialize", std::slice::from_ref(&rd_s));
prop_assert!(ok_d, "deserialize must succeed; exc={:?}", exc_d);
prop_assert_eq!(&rd_d, &bytes,
"deserialize(serialize(b)) must equal b; got={:?}, want={:?}, bin={:?}",
rd_d, bytes, rd_s);
}
#[test]
fn stdlib_jsonserialize_jsondeserialize_bytearray_roundtrip(
bytes in prop::collection::vec(any::<u8>(), 0..=96),
) {
let (ok_s, rd_s, exc_s) = call_stdlib("jsonserialize", std::slice::from_ref(&bytes));
prop_assert!(ok_s, "jsonSerialize must succeed; exc={:?}", exc_s);
prop_assert!(!rd_s.is_empty(),
"jsonSerialize of ByteArray must produce non-empty JSON");
prop_assume!(rd_s.len() <= 255);
let (ok_d, rd_d, exc_d) = call_stdlib("jsondeserialize", std::slice::from_ref(&rd_s));
prop_assert!(ok_d, "jsonDeserialize must succeed; exc={:?}", exc_d);
prop_assert_eq!(&rd_d, &bytes,
"jsonDeserialize(jsonSerialize(b)) must equal b; got={:?}, want={:?}, json={:?}",
rd_d, bytes,
std::str::from_utf8(&rd_s).ok());
}
#[test]
fn stdlib_deserialize_malformed_returns_null_no_panic(
extra in prop::collection::vec(any::<u8>(), 0..=32),
) {
let mut garbage = vec![0x00, 0xFF];
garbage.extend_from_slice(&extra);
let (ok, rd, exc) = call_stdlib("deserialize", std::slice::from_ref(&garbage));
prop_assert!(ok,
"deserialize of malformed bytes must NOT fault at host level; \
input={:?}, exc={:?}", garbage, exc);
prop_assert!(rd.is_empty(),
"deserialize of malformed (truncated-payload) binary must return \
empty (Null); input={:?}, got={:?}",
garbage, rd);
}
#[test]
fn stdlib_jsondeserialize_malformed_returns_null_no_panic(
garbage in prop_oneof![
prop::collection::vec(128u8..=255u8, 1..=64),
prop::collection::vec(any::<u8>().prop_filter("not a JSON object byte", |b| *b != b'{'), 1..=64),
],
) {
let (ok, rd, exc) = call_stdlib("jsondeserialize", std::slice::from_ref(&garbage));
prop_assert!(ok,
"jsonDeserialize of malformed bytes must NOT fault; \
input.len={}, exc={:?}", garbage.len(), exc);
prop_assert!(rd.is_empty(),
"jsonDeserialize of malformed bytes must return empty (Null); \
input.len={}, got={:?}", garbage.len(), rd);
}
}
#[test]
fn stdlib_deserialize_unknown_tag_falls_back_to_null() {
let (ok, rd, _exc) = call_stdlib("deserialize", &[vec![0x05, 0xAA, 0xBB]]);
assert!(ok, "unknown-tag deserialize must not fault");
assert!(
rd.is_empty(),
"unknown-tag deserialize must yield Null (empty return_data); got {:?}",
rd
);
}