use blvm_consensus::*;
use blvm_consensus::script;
use blvm_consensus::constants::MAX_STACK_SIZE;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_op_dup_duplicates(
item in prop::collection::vec(any::<u8>(), 0..50)
) {
let mut script = vec![item.len() as u8]; script.extend_from_slice(&item);
script.push(0x76);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok());
if result.unwrap() && stack.len() >= 2 {
prop_assert_eq!(stack[stack.len() - 2], item);
prop_assert_eq!(stack[stack.len() - 1], item);
}
}
}
proptest! {
#[test]
fn prop_script_with_op_equalverify(
item1 in prop::collection::vec(any::<u8>(), 0..10),
item2 in prop::collection::vec(any::<u8>(), 0..10)
) {
let mut script = Vec::new();
script.push(item1.len() as u8);
script.extend_from_slice(&item1);
script.push(item2.len() as u8);
script.extend_from_slice(&item2);
script.push(0x88);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok() || result.is_err());
if result.is_ok() && result.unwrap() {
prop_assert!(item1 == item2, "OP_EQUALVERIFY should succeed only if equal");
}
}
}
proptest! {
#[test]
fn prop_op_hash160_fixed_length(
input in prop::collection::vec(any::<u8>(), 1..75) ) {
let mut script = Vec::new();
script.push(input.len() as u8);
script.extend_from_slice(&input);
script.push(0xa9);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok());
if result.unwrap() && !stack.is_empty() {
prop_assert_eq!(stack[0].len(), 20,
"OP_HASH160 should produce 20-byte hash");
}
}
}
proptest! {
#[test]
fn prop_op_checksig_stack_requirements(
sig in prop::collection::vec(any::<u8>(), 0..75),
pubkey in prop::collection::vec(any::<u8>(), 0..75)
) {
let mut script = Vec::new();
script.push(sig.len() as u8);
script.extend_from_slice(&sig);
script.push(pubkey.len() as u8);
script.extend_from_slice(&pubkey);
script.push(0xac);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok());
}
}
proptest! {
#[test]
fn prop_op_1_to_16_values(
opcode in 0x51u8..=0x60u8 ) {
let script = vec![opcode];
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok());
if result.unwrap() && !stack.is_empty() {
let expected_value = (opcode - 0x50) as u8;
prop_assert!(stack[0].len() > 0);
prop_assert!(expected_value >= 1 && expected_value <= 16);
}
}
}
proptest! {
#[test]
fn prop_op_0_pushes_empty() {
let script = vec![0x00]; let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok());
if result.unwrap() && !stack.is_empty() {
prop_assert!(stack[0].is_empty(), "OP_0 should push empty array");
}
}
}
proptest! {
#[test]
fn prop_script_with_op_if(
condition in prop::collection::vec(any::<u8>(), 0..10)
) {
let mut script = Vec::new();
script.push(condition.len() as u8);
script.extend_from_slice(&condition);
script.push(0x63);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok() || result.is_err());
}
}
proptest! {
#[test]
fn prop_pushdata_preserves_data(
data in prop::collection::vec(any::<u8>(), 1..75) ) {
let mut script = Vec::new();
let len = data.len();
if len <= 75 {
script.push(len as u8);
script.extend_from_slice(&data);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok() || result.is_err());
if result.is_ok() && result.unwrap() && !stack.is_empty() {
prop_assert_eq!(stack[0], data);
}
}
}
}
proptest! {
#[test]
fn prop_script_arithmetic_opcodes(
opcode in 0x93u8..=0x95u8, a in 0u8..=10u8,
b in 0u8..=10u8
) {
let script = vec![0x51 + a.min(16), 0x51 + b.min(16), opcode];
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok() || result.is_err());
}
}
proptest! {
#[test]
fn prop_script_op_equal(
a in 0u8..=16u8,
b in 0u8..=16u8
) {
let script = vec![0x51 + a.min(16), 0x51 + b.min(16), 0x87]; let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok() || result.is_err());
}
}
proptest! {
#[test]
fn prop_script_op_verify(
value in 1u8..=16u8
) {
let script = vec![0x51 + value.min(16), 0x69]; let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok() || result.is_err());
}
}
proptest! {
#[test]
fn prop_script_op_return_always_fails() {
let script = vec![0x51, 0x6a]; let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok());
if let Ok(success) = result {
prop_assert!(!success, "OP_RETURN should always fail");
}
}
}
proptest! {
#[test]
fn prop_op_sha256_fixed_length(
input in prop::collection::vec(any::<u8>(), 1..75)
) {
let mut script = Vec::new();
script.push(input.len() as u8);
script.extend_from_slice(&input);
script.push(0xa8);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok());
if result.unwrap() && !stack.is_empty() {
prop_assert_eq!(stack[0].len(), 32,
"OP_SHA256 should produce 32-byte hash");
}
}
}
proptest! {
#[test]
fn prop_op_ripemd160_fixed_length(
input in prop::collection::vec(any::<u8>(), 1..75)
) {
let mut script = Vec::new();
script.push(input.len() as u8);
script.extend_from_slice(&input);
script.push(0xa6);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok());
if result.unwrap() && !stack.is_empty() {
prop_assert_eq!(stack[0].len(), 20,
"OP_RIPEMD160 should produce 20-byte hash");
}
}
}
proptest! {
#[test]
fn prop_stack_size_limit_enforced(
push_count in 0usize..(MAX_STACK_SIZE.min(100))
) {
let mut script = Vec::new();
for _ in 0..push_count {
script.push(0x51); }
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(stack.len() <= MAX_STACK_SIZE);
if push_count > MAX_STACK_SIZE {
prop_assert!(result.is_err() || !result.unwrap());
}
}
}
proptest! {
#[test]
fn prop_script_op_2drop(
a in 1u8..=16u8,
b in 1u8..=16u8
) {
let script = vec![0x51 + a.min(16), 0x51 + b.min(16), 0x6d]; let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok() || result.is_err());
}
}
proptest! {
#[test]
fn prop_script_op_swap(
a in 1u8..=16u8,
b in 1u8..=16u8
) {
let script = vec![0x51 + a.min(16), 0x51 + b.min(16), 0x7c]; let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0);
prop_assert!(result.is_ok() || result.is_err());
}
}