use blvm_consensus::constants::MAX_STACK_SIZE;
use blvm_consensus::script;
use proptest::prelude::*;
use blvm_consensus::opcodes::{
OP_0, OP_1, OP_1_RANGE_END, OP_1_RANGE_START, OP_2DROP, OP_CHECKSIG, OP_DUP, OP_EQUAL,
OP_EQUALVERIFY, OP_HASH160, OP_IF, OP_N_BASE, OP_RETURN, OP_RIPEMD160, OP_SHA256, OP_SWAP,
OP_VERIFY,
};
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(OP_DUP);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
prop_assert!(result.is_ok());
if result.unwrap() && stack.len() >= 2 {
prop_assert_eq!(stack[stack.len() - 2].as_slice(), item.as_slice());
prop_assert_eq!(stack[stack.len() - 1].as_slice(), item.as_slice());
}
}
}
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(OP_EQUALVERIFY);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
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(OP_HASH160);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
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(OP_CHECKSIG);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
prop_assert!(result.is_ok());
}
}
proptest! {
#[test]
fn prop_op_1_to_16_values(
opcode in OP_1_RANGE_START..=OP_1_RANGE_END ) {
let script = vec![opcode];
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
prop_assert!(result.is_ok());
if result.unwrap() && !stack.is_empty() {
let expected_value = (opcode - 0x50);
prop_assert!(!stack[0].is_empty());
prop_assert!((1..=16).contains(&expected_value));
}
}
}
#[test]
fn prop_op_0_pushes_empty() {
let script = vec![OP_0]; let mut stack = Vec::new();
let result = script::eval_script(
&script,
&mut stack,
0,
blvm_consensus::script::SigVersion::Base,
);
assert!(result.is_ok());
if result.unwrap() && !stack.is_empty() {
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(OP_IF);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
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, blvm_consensus::script::SigVersion::Base);
prop_assert!(result.is_ok() || result.is_err());
if result.is_ok() && result.unwrap() && !stack.is_empty() {
prop_assert_eq!(stack[0].as_slice(), data.as_slice());
}
}
}
}
proptest! {
#[test]
fn prop_script_arithmetic_opcodes(
opcode in 0x93u8..=0x95u8, a in 0u8..=10u8,
b in 0u8..=10u8
) {
let script = vec![OP_N_BASE + a.min(16), OP_N_BASE + b.min(16), opcode];
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
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![OP_N_BASE + a.min(16), OP_N_BASE + b.min(16), OP_EQUAL];
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
prop_assert!(result.is_ok() || result.is_err());
}
}
proptest! {
#[test]
fn prop_script_op_verify(
value in 1u8..=16u8
) {
let script = vec![OP_N_BASE + value.min(16), OP_VERIFY];
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
prop_assert!(result.is_ok() || result.is_err());
}
}
#[test]
fn prop_script_op_return_always_fails() {
let script = vec![OP_1, OP_RETURN];
let mut stack = Vec::new();
let result = script::eval_script(
&script,
&mut stack,
0,
blvm_consensus::script::SigVersion::Base,
);
assert!(result.is_ok());
if let Ok(success) = result {
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(OP_SHA256);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
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(OP_RIPEMD160);
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
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(OP_1);
}
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
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![OP_N_BASE + a.min(16), OP_N_BASE + b.min(16), OP_2DROP];
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
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![OP_N_BASE + a.min(16), OP_N_BASE + b.min(16), OP_SWAP];
let mut stack = Vec::new();
let result = script::eval_script(&script, &mut stack, 0, blvm_consensus::script::SigVersion::Base);
prop_assert!(result.is_ok() || result.is_err());
}
}