#![allow(unused_imports)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_lossless)]
use neo_devpack_solidity::runtime::execution::{ExecutionContext, StackItem};
use neo_devpack_solidity::runtime::RuntimeConfig;
use proptest::prelude::*;
const OP_SIGN: u8 = 0x99;
const OP_ABS: u8 = 0x9A;
const OP_NEGATE: u8 = 0x9B;
const OP_INC: u8 = 0x9C;
const OP_DEC: u8 = 0x9D;
const OP_POW: u8 = 0xA3;
const OP_SQRT: u8 = 0xA4;
fn ctx_for_opcode(opcode: u8) -> ExecutionContext {
let mut ctx = ExecutionContext::new(&RuntimeConfig::default()).expect("context init");
ctx.initialize(&[opcode], &[]).expect("init bytecode");
ctx
}
fn step_unary(opcode: u8, value: StackItem) -> Result<StackItem, String> {
let mut ctx = ctx_for_opcode(opcode);
ctx.push_stack(value).map_err(|e| e.to_string())?;
ctx.step().map_err(|e| e.to_string())?;
let top = ctx.peek_stack().map_err(|e| e.to_string())?.clone();
Ok(top)
}
fn step_binary(opcode: u8, a: StackItem, b: StackItem) -> Result<StackItem, String> {
let mut ctx = ctx_for_opcode(opcode);
ctx.push_stack(a).map_err(|e| e.to_string())?;
ctx.push_stack(b).map_err(|e| e.to_string())?;
ctx.step().map_err(|e| e.to_string())?;
let top = ctx.peek_stack().map_err(|e| e.to_string())?.clone();
Ok(top)
}
fn as_i64(item: &StackItem) -> Option<i64> {
match item {
StackItem::Integer(v) => Some(*v),
StackItem::UnsignedInteger(v) => i64::try_from(*v).ok(),
StackItem::Boolean(b) => Some(if *b { 1 } else { 0 }),
_ => None,
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn pow_small_exponent_matches_reference(
a in -100i64..=100i64,
b in 0u32..=10,
) {
let result = step_binary(OP_POW, StackItem::Integer(a), StackItem::Integer(b as i64));
match a.checked_pow(b) {
Some(expected) => {
let v = result.expect("POW must succeed for in-range inputs");
prop_assert_eq!(as_i64(&v), Some(expected),
"POW({}, {}) returned {:?} (expected {})", a, b, v, expected);
}
None => {
prop_assert!(result.is_err(),
"POW({}, {}) should overflow but returned {:?}", a, b, result);
}
}
}
#[test]
fn pow_negative_exponent_errors(
a in -1000i64..=1000i64,
b in i64::MIN..0i64,
) {
let result = step_binary(OP_POW, StackItem::Integer(a), StackItem::Integer(b));
prop_assert!(result.is_err(),
"POW with negative exponent {} must error, got {:?}", b, result);
}
#[test]
fn pow_huge_exponent_errors(
a in 2i64..=10i64,
b in (u32::MAX as u64 + 1)..u64::MAX,
) {
let result = step_binary(OP_POW, StackItem::Integer(a), StackItem::UnsignedInteger(b));
prop_assert!(result.is_err(),
"POW with exponent > u32::MAX ({}) must error, got {:?}", b, result);
}
#[test]
fn pow_zero_exponent_is_one(
a in any::<i64>(),
) {
let result = step_binary(OP_POW, StackItem::Integer(a), StackItem::Integer(0));
let v = result.expect("POW(a, 0) must succeed");
prop_assert_eq!(as_i64(&v), Some(1));
}
#[test]
fn pow_unsigned_base_matches_reference(
a in 0u64..=1_000_000,
b in 0u32..=5,
) {
let result = step_binary(OP_POW, StackItem::UnsignedInteger(a), StackItem::Integer(b as i64));
match a.checked_pow(b) {
Some(expected) => {
let v = result.expect("POW(unsigned) must succeed");
match v {
StackItem::UnsignedInteger(got) => prop_assert_eq!(got, expected),
other => prop_assert!(false,
"POW(unsigned base) returned non-unsigned variant {:?}", other),
}
}
None => {
prop_assert!(result.is_err(),
"POW(unsigned) overflow must error");
}
}
}
#[test]
fn pow_rejects_non_integer_operands(
b in 0i64..=10,
) {
let cases: Vec<StackItem> = vec![
StackItem::Boolean(true),
StackItem::Null,
StackItem::byte_array(vec![1, 2, 3]),
];
for op in cases {
let r_base = step_binary(OP_POW, op.clone(), StackItem::Integer(b));
prop_assert!(r_base.is_err(),
"POW with non-integer base {:?} must error, got {:?}", op, r_base);
let r_exp = step_binary(OP_POW, StackItem::Integer(2), op.clone());
prop_assert!(r_exp.is_err(),
"POW with non-integer exponent {:?} must error, got {:?}", op, r_exp);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(128))]
#[test]
fn sqrt_matches_floor_sqrt(
n in 0i64..=(u32::MAX as i64),
) {
let result = step_unary(OP_SQRT, StackItem::Integer(n));
let v = result.expect("SQRT must succeed for non-negative input");
let got = as_i64(&v).expect("SQRT result must be integer-shaped");
let expected = (n as f64).sqrt().floor() as i64;
prop_assert_eq!(got, expected,
"SQRT({}) = {} (expected {})", n, got, expected);
prop_assert!(got >= 0);
prop_assert!(got.saturating_mul(got) <= n,
"SQRT({}) lower bound: {}^2 = {} > {}", n, got, got.saturating_mul(got), n);
let next = got + 1;
prop_assert!(next.saturating_mul(next) > n || next.saturating_mul(next) < 0,
"SQRT({}) upper bound: ({}+1)^2 = {} <= {}", n, got, next.saturating_mul(next), n);
}
#[test]
fn sqrt_negative_errors(
n in i64::MIN..0i64,
) {
let result = step_unary(OP_SQRT, StackItem::Integer(n));
prop_assert!(result.is_err(),
"SQRT({}) must error, got {:?}", n, result);
}
#[test]
fn sqrt_rejects_non_integer(_dummy in any::<bool>()) {
for op in [
StackItem::Boolean(true),
StackItem::Null,
StackItem::byte_array(vec![1, 2, 3]),
] {
let result = step_unary(OP_SQRT, op.clone());
prop_assert!(result.is_err(),
"SQRT({:?}) must error, got {:?}", op, result);
}
}
}
#[test]
fn sqrt_zero_is_zero() {
let v = step_unary(OP_SQRT, StackItem::Integer(0)).expect("ok");
assert_eq!(as_i64(&v), Some(0));
}
#[test]
fn sqrt_one_is_one() {
let v = step_unary(OP_SQRT, StackItem::Integer(1)).expect("ok");
assert_eq!(as_i64(&v), Some(1));
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(128))]
#[test]
fn sign_signed_matches_signum(
v in any::<i64>(),
) {
let result = step_unary(OP_SIGN, StackItem::Integer(v));
let item = result.expect("SIGN must succeed");
prop_assert_eq!(as_i64(&item), Some(v.signum()));
}
#[test]
fn sign_unsigned_is_zero_or_one(
v in any::<u64>(),
) {
let result = step_unary(OP_SIGN, StackItem::UnsignedInteger(v));
let item = result.expect("SIGN(unsigned) must succeed");
let expected = if v == 0 { 0i64 } else { 1 };
prop_assert_eq!(as_i64(&item), Some(expected));
}
#[test]
fn sign_boolean(
b in any::<bool>(),
) {
let result = step_unary(OP_SIGN, StackItem::Boolean(b));
let item = result.expect("SIGN(bool) must succeed");
let expected = if b { 1i64 } else { 0 };
prop_assert_eq!(as_i64(&item), Some(expected));
}
#[test]
fn sign_rejects_unsupported(_dummy in any::<bool>()) {
for op in [
StackItem::Null,
StackItem::byte_array(vec![]),
StackItem::byte_array(vec![0u8; 32]),
] {
let r = step_unary(OP_SIGN, op.clone());
prop_assert!(r.is_err(),
"SIGN({:?}) must error, got {:?}", op, r);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(128))]
#[test]
fn abs_signed_matches_wrapping_abs(
v in (i64::MIN + 1)..=i64::MAX,
) {
let result = step_unary(OP_ABS, StackItem::Integer(v));
let item = result.expect("ABS must succeed when not i64::MIN");
let got = as_i64(&item).expect("ABS produces an integer");
prop_assert!(got >= 0, "ABS({}) = {} must be non-negative", v, got);
prop_assert_eq!(got, v.wrapping_abs(),
"ABS({}) = {} (expected {})", v, got, v.wrapping_abs());
}
#[test]
fn abs_unsigned_identity(
v in any::<u64>(),
) {
let result = step_unary(OP_ABS, StackItem::UnsignedInteger(v));
let item = result.expect("ABS(unsigned) must succeed");
match item {
StackItem::UnsignedInteger(got) => prop_assert_eq!(got, v),
other => prop_assert!(false, "ABS(unsigned) returned non-unsigned {:?}", other),
}
}
}
#[test]
fn abs_i64_min_must_error() {
let r = step_unary(OP_ABS, StackItem::Integer(i64::MIN));
assert!(r.is_err(), "ABS(i64::MIN) must error, got {:?}", r);
}
#[test]
fn abs_rejects_non_numeric() {
for op in [
StackItem::Boolean(true),
StackItem::Null,
StackItem::byte_array(vec![]),
] {
let r = step_unary(OP_ABS, op.clone());
assert!(r.is_err(), "ABS({:?}) must error, got {:?}", op, r);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(128))]
#[test]
fn negate_signed_matches(
v in (i64::MIN + 1)..=i64::MAX,
) {
let result = step_unary(OP_NEGATE, StackItem::Integer(v));
let item = result.expect("NEGATE must succeed when not i64::MIN");
prop_assert_eq!(as_i64(&item), Some(-v),
"NEGATE({}) failed", v);
}
#[test]
fn negate_unsigned_in_signed_range(
v in 0u64..=(i64::MAX as u64),
) {
let result = step_unary(OP_NEGATE, StackItem::UnsignedInteger(v));
let item = result.expect("NEGATE(unsigned in range) must succeed");
prop_assert_eq!(as_i64(&item), Some(-(v as i64)));
}
#[test]
fn negate_unsigned_out_of_range_errors(
v in (i64::MAX as u64 + 1)..=u64::MAX,
) {
let result = step_unary(OP_NEGATE, StackItem::UnsignedInteger(v));
prop_assert!(result.is_err(),
"NEGATE(unsigned {}) must error, got {:?}", v, result);
}
}
#[test]
fn negate_i64_min_must_error() {
let r = step_unary(OP_NEGATE, StackItem::Integer(i64::MIN));
assert!(r.is_err(), "NEGATE(i64::MIN) must error, got {:?}", r);
}
#[test]
fn negate_rejects_non_numeric() {
for op in [
StackItem::Boolean(true),
StackItem::Null,
StackItem::byte_array(vec![]),
] {
let r = step_unary(OP_NEGATE, op.clone());
assert!(r.is_err(), "NEGATE({:?}) must error, got {:?}", op, r);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(128))]
#[test]
fn inc_signed_in_range(
v in i64::MIN..i64::MAX,
) {
let result = step_unary(OP_INC, StackItem::Integer(v));
let item = result.expect("INC must succeed in range");
prop_assert_eq!(as_i64(&item), Some(v + 1),
"INC({}) failed", v);
}
#[test]
fn dec_signed_in_range(
v in (i64::MIN + 1)..=i64::MAX,
) {
let result = step_unary(OP_DEC, StackItem::Integer(v));
let item = result.expect("DEC must succeed in range");
prop_assert_eq!(as_i64(&item), Some(v - 1),
"DEC({}) failed", v);
}
#[test]
fn inc_unsigned_in_range(
v in 0u64..u64::MAX,
) {
let result = step_unary(OP_INC, StackItem::UnsignedInteger(v));
let item = result.expect("INC(unsigned in range) must succeed");
match item {
StackItem::UnsignedInteger(got) => prop_assert_eq!(got, v + 1),
other => prop_assert!(false,
"INC(unsigned) returned non-unsigned {:?}", other),
}
}
#[test]
fn dec_unsigned_in_range(
v in 1u64..=u64::MAX,
) {
let result = step_unary(OP_DEC, StackItem::UnsignedInteger(v));
let item = result.expect("DEC(unsigned in range) must succeed");
match item {
StackItem::UnsignedInteger(got) => prop_assert_eq!(got, v - 1),
other => prop_assert!(false,
"DEC(unsigned) returned non-unsigned {:?}", other),
}
}
}
#[test]
fn inc_i64_max_must_error_strict() {
let r = step_unary(OP_INC, StackItem::Integer(i64::MAX));
assert!(
r.is_err(),
"INC(i64::MAX) under strict mode must error, got {:?}",
r
);
}
#[test]
fn dec_i64_min_must_error_strict() {
let r = step_unary(OP_DEC, StackItem::Integer(i64::MIN));
assert!(
r.is_err(),
"DEC(i64::MIN) under strict mode must error, got {:?}",
r
);
}
#[test]
fn inc_unsigned_max_must_error_strict() {
let r = step_unary(OP_INC, StackItem::UnsignedInteger(u64::MAX));
assert!(
r.is_err(),
"INC(u64::MAX) under strict mode must error, got {:?}",
r
);
}
#[test]
fn dec_unsigned_zero_must_error_strict() {
let r = step_unary(OP_DEC, StackItem::UnsignedInteger(0));
assert!(
r.is_err(),
"DEC(0u64) under strict mode must error, got {:?}",
r
);
}
#[test]
fn inc_rejects_non_numeric() {
for op in [
StackItem::Boolean(true),
StackItem::Null,
StackItem::byte_array(vec![]),
] {
let r = step_unary(OP_INC, op.clone());
assert!(r.is_err(), "INC({:?}) must error, got {:?}", op, r);
}
}
#[test]
fn dec_rejects_non_numeric() {
for op in [
StackItem::Boolean(true),
StackItem::Null,
StackItem::byte_array(vec![]),
] {
let r = step_unary(OP_DEC, op.clone());
assert!(r.is_err(), "DEC({:?}) must error, got {:?}", op, r);
}
}