#![allow(
clippy::string_lit_as_bytes,
clippy::manual_string_new,
clippy::needless_pass_by_value,
clippy::uninlined_format_args,
clippy::needless_collect,
clippy::cast_sign_loss
)]
use std::collections::BTreeMap;
use std::sync::Arc;
use wacc::{
storage::{Pairs, Stack},
types::{CheckCount, ContextPath, FuelAmount, LogSize, WasmPtr, WasmSize},
vm::{Builder, Context, Value},
};
use wasmtime::StoreLimitsBuilder;
#[derive(Default, Clone)]
struct Kvp {
pub pairs: BTreeMap<String, Value>,
}
impl Pairs for Kvp {
fn get(&self, key: &str) -> Option<Value> {
self.pairs.get(key).cloned()
}
fn put(&mut self, key: &str, value: &Value) -> Option<Value> {
self.pairs.insert(key.to_string(), value.clone())
}
}
#[derive(Default, Clone)]
struct Stk {
pub stack: Vec<Value>,
}
impl Stack for Stk {
fn push(&mut self, value: Value) {
self.stack.push(value);
}
fn pop(&mut self) -> Option<Value> {
self.stack.pop()
}
fn top(&self) -> Option<Value> {
self.stack.last().cloned()
}
fn peek(&self, idx: usize) -> Option<Value> {
if idx >= self.stack.len() {
return None;
}
Some(self.stack[self.stack.len() - 1 - idx].clone())
}
fn len(&self) -> usize {
self.stack.len()
}
fn is_empty(&self) -> bool {
self.stack.is_empty()
}
}
#[test]
fn test_empty_stack_operations() {
let kvp = Kvp::default();
let pstack = Stk::default();
let rstack = Stk::default();
let mut context = Context {
current: Box::new(kvp.clone()),
proposed: Box::new(kvp),
pstack: Box::new(pstack),
rstack: Box::new(rstack),
check_count: CheckCount::zero(),
write_idx: 0,
context: ContextPath::root(),
log: Vec::default(),
limiter: StoreLimitsBuilder::new().build(),
};
let result = context.pop();
assert_eq!(result.i32(), Some(0));
match context.rstack.top() {
Some(Value::Failure(_)) => {} other => panic!("Expected Failure, got {other:?}"),
}
}
#[test]
fn test_missing_key_access() {
let kvp = Kvp::default();
let pstack = Stk::default();
let rstack = Stk::default();
let mut context = Context {
current: Box::new(kvp.clone()),
proposed: Box::new(kvp),
pstack: Box::new(pstack),
rstack: Box::new(rstack),
check_count: CheckCount::zero(),
write_idx: 0,
context: ContextPath::root(),
log: Vec::default(),
limiter: StoreLimitsBuilder::new().build(),
};
let result = context.push("nonexistent");
assert_eq!(result.i32(), Some(0)); }
#[test]
fn test_check_count_wraparound() {
let kvp = Kvp::default();
let pstack = Stk::default();
let rstack = Stk::default();
let mut context = Context {
current: Box::new(kvp.clone()),
proposed: Box::new(kvp),
pstack: Box::new(pstack),
rstack: Box::new(rstack),
check_count: CheckCount::new(CheckCount::MAX),
write_idx: 0,
context: ContextPath::root(),
log: Vec::default(),
limiter: StoreLimitsBuilder::new().build(),
};
let result = context.check_fail("test");
assert_eq!(result.i32(), Some(0));
match context.rstack.top() {
Some(Value::Failure(msg)) => {
assert!(msg.contains("maximum check count exceeded"));
}
other => panic!("Expected Failure with max count message, got {other:?}"),
}
}
#[test]
fn test_log_buffer_full() {
let kvp = Kvp::default();
let pstack = Stk::default();
let rstack = Stk::default();
let mut context = Context {
current: Box::new(kvp.clone()),
proposed: Box::new(kvp),
pstack: Box::new(pstack),
rstack: Box::new(rstack),
check_count: CheckCount::zero(),
write_idx: 0,
context: ContextPath::root(),
log: vec![0u8; LogSize::MAX - 5], limiter: StoreLimitsBuilder::new().build(),
};
let result = context.log("test message"); assert_eq!(result.i32(), Some(0)); }
#[test]
fn test_zero_fuel() {
let kvp = Kvp::default();
let pstack = Stk::default();
let rstack = Stk::default();
let context = Context {
current: Box::new(kvp.clone()),
proposed: Box::new(kvp),
pstack: Box::new(pstack),
rstack: Box::new(rstack),
check_count: CheckCount::zero(),
write_idx: 0,
context: ContextPath::root(),
log: Vec::default(),
limiter: StoreLimitsBuilder::new().build(),
};
let result = Builder::new()
.with_fuel(FuelAmount::new(1))
.with_context(context)
.with_bytes([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, 0x00, 0x01,
0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00,
0x00, 0x0a, 0x06, 0x01, 0x04, 0x00, 0x41, 0x01, 0x0b,
])
.try_build();
assert!(result.is_ok());
}
#[test]
fn test_wasm_ptr_max_value() {
let ptr = WasmPtr::new(u32::MAX);
let size = WasmSize::new(1);
assert!(ptr.would_overflow(size));
}
#[test]
fn test_wasm_size_zero() {
let size = WasmSize::new(0);
assert!(size.is_zero());
let ptr = WasmPtr::new(1000);
assert!(!ptr.would_overflow(size)); }
#[test]
fn test_context_path_empty_segment() {
let path = ContextPath::root();
assert!(path.branch("").is_none());
}
#[test]
fn test_context_path_with_separator() {
let path = ContextPath::root();
assert!(path.branch("bad/segment").is_none());
}
#[test]
fn test_context_path_null_byte() {
assert!(ContextPath::new_checked("path\0with\0null").is_none());
}
#[test]
fn test_value_empty_data() {
let value: Value = vec![].into();
match value {
Value::Bin { data, .. } => assert_eq!(data.len(), 0),
_ => panic!("Expected Bin value"),
}
}
#[test]
fn test_value_large_data() {
let large_data = vec![0u8; 1024 * 1024];
let value: Value = large_data.into();
match value {
Value::Bin { data, .. } => {
assert_eq!(data.len(), 1024 * 1024);
let _cloned = Value::Bin {
hint: String::new(),
data: Arc::clone(&data),
};
assert_eq!(data.len(), 1024 * 1024);
}
_ => panic!("Expected Bin value"),
}
}
#[test]
fn test_check_count_boundary() {
let count = CheckCount::new(CheckCount::MAX - 1);
assert!(count.increment().is_some());
let count = CheckCount::new(CheckCount::MAX);
assert!(count.increment().is_none());
}
#[test]
fn test_fuel_amount_boundary() {
assert!(FuelAmount::new_checked(1000).is_some());
assert!(FuelAmount::new_checked(FuelAmount::MAX).is_some());
assert!(FuelAmount::new_checked(FuelAmount::MAX + 1).is_none());
assert!(FuelAmount::new_checked(0).is_none());
}
#[test]
fn test_wasm_size_max_boundary() {
assert!(WasmSize::new_checked(WasmSize::MAX_SIZE).is_some());
assert!(WasmSize::new_checked(WasmSize::MAX_SIZE + 1).is_none());
}