use crate::ffi::{
bitstream::CBitstream,
bitstring::CBitstring,
dag::{
computeAnnotatedMerkleRoot, fillWitnessData, verifyNoDuplicateIdentityRoots, CAnalyses,
CCombinatorCounters,
},
deserialize::{decodeMallocDag, decodeWitnessData},
eval::{analyseBounds, evalTCOProgram},
sha256::CSha256Midstate,
type_inference::mallocTypeInference,
ubounded, SimplicityErr, UBOUNDED_MAX,
};
use libc::{c_void, size_t};
use std::ptr;
#[cfg(feature = "test-utils")]
pub mod ffi {
use crate::ffi::ubounded;
use libc::size_t;
extern "C" {
pub static sizeof_ctx8Pruned: size_t;
pub static ctx8Pruned: [u8; 5015];
pub static ctx8Pruned_amr: [u32; 8];
pub static ctx8Pruned_cmr: [u32; 8];
pub static ctx8Pruned_imr: [u32; 8];
pub static ctx8Pruned_cost: ubounded;
pub static sizeof_ctx8Unpruned: size_t;
pub static ctx8Unpruned: [u8; 4809];
pub static ctx8Unpruned_amr: [u32; 8];
pub static ctx8Unpruned_cmr: [u32; 8];
pub static ctx8Unpruned_imr: [u32; 8];
pub static ctx8Unpruned_cost: ubounded;
pub static sizeof_schnorr0: size_t;
pub static schnorr0: [u8; 137];
pub static schnorr0_amr: [u32; 8];
pub static schnorr0_cmr: [u32; 8];
pub static schnorr0_imr: [u32; 8];
pub static schnorr0_cost: ubounded;
pub static sizeof_schnorr6: size_t;
pub static schnorr6: [u8; 137];
pub static schnorr6_amr: [u32; 8];
pub static schnorr6_cmr: [u32; 8];
pub static schnorr6_imr: [u32; 8];
pub static schnorr6_cost: ubounded;
}
}
#[derive(Copy, Clone, Debug)]
pub struct TestOutput {
pub amr: CSha256Midstate,
pub cmr: CSha256Midstate,
pub imr: CSha256Midstate,
pub cost_bound: ubounded,
pub eval_result: SimplicityErr,
}
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
pub enum TestUpTo {
DecodeProgram,
DecodeWitness,
TypeInference,
FillWitnessData,
ComputeAmr,
ComputeImr,
ComputeCostUnbounded,
ComputeCostBounded,
CheckCellCount,
CheckBudget,
CheckOneOne,
Everything,
}
pub fn run_test(
program: &[u8],
target_amr: &[u32; 8],
target_cmr: &[u32; 8],
target_imr: &[u32; 8],
cost_bound: ubounded,
) {
let result = run_program(program, TestUpTo::Everything).expect("running program");
assert_eq!(result.amr, CSha256Midstate { s: *target_amr });
assert_eq!(result.cmr, CSha256Midstate { s: *target_cmr });
assert_eq!(result.imr, CSha256Midstate { s: *target_imr });
assert_eq!(result.cost_bound, cost_bound);
assert_eq!(result.eval_result, SimplicityErr::NoError);
}
pub fn run_test_fail(
program: &[u8],
target_result: SimplicityErr,
target_amr: &[u32; 8],
target_cmr: &[u32; 8],
target_imr: &[u32; 8],
cost_bound: ubounded,
) {
let result = run_program(program, TestUpTo::Everything).expect("running program");
assert_eq!(result.amr, CSha256Midstate { s: *target_amr });
assert_eq!(result.cmr, CSha256Midstate { s: *target_cmr });
assert_eq!(result.imr, CSha256Midstate { s: *target_imr });
assert_eq!(result.cost_bound, cost_bound);
assert_eq!(result.eval_result, target_result);
}
struct FreeOnDrop(*mut c_void);
impl Drop for FreeOnDrop {
fn drop(&mut self) {
unsafe {
libc::free(self.0);
}
}
}
pub fn run_program(program: &[u8], test_up_to: TestUpTo) -> Result<TestOutput, SimplicityErr> {
let mut result = TestOutput {
amr: CSha256Midstate::default(),
cmr: CSha256Midstate::default(),
imr: CSha256Midstate::default(),
cost_bound: 0,
eval_result: SimplicityErr::NoError,
};
let mut stream = CBitstream::from(program);
let mut witness = CBitstring::default();
let mut census = CCombinatorCounters::default();
unsafe {
let mut dag = ptr::null_mut();
let len =
SimplicityErr::from_i32(decodeMallocDag(&mut dag, &mut census, &mut stream))? as usize;
assert!(!dag.is_null());
let _d1 = FreeOnDrop(dag as *mut c_void);
if test_up_to <= TestUpTo::DecodeProgram {
return Ok(result);
}
decodeWitnessData(&mut witness, &mut stream).into_result()?;
if test_up_to <= TestUpTo::DecodeWitness {
return Ok(result);
}
result.cmr = (*dag.offset(len as isize - 1)).cmr;
let mut type_dag = ptr::null_mut();
mallocTypeInference(&mut type_dag, dag, len, &census).into_result()?;
assert!(!type_dag.is_null());
let _d2 = FreeOnDrop(type_dag as *mut c_void);
if test_up_to <= TestUpTo::TypeInference {
return Ok(result);
}
fillWitnessData(dag, type_dag, len as size_t, witness).into_result()?;
if test_up_to <= TestUpTo::FillWitnessData {
return Ok(result);
}
let mut analyses = vec![CAnalyses::default(); len];
computeAnnotatedMerkleRoot(analyses.as_mut_ptr(), dag, type_dag, len);
result.amr = analyses[len - 1].annotated_merkle_root;
if test_up_to <= TestUpTo::ComputeAmr {
return Ok(result);
}
verifyNoDuplicateIdentityRoots(&mut result.imr, dag, type_dag, len).into_result()?;
if test_up_to <= TestUpTo::ComputeImr {
return Ok(result);
}
let mut cell_bound: ubounded = 0;
let mut word_bound: ubounded = 0;
let mut frame_bound: ubounded = 0;
let mut cost_bound: ubounded = 0;
analyseBounds(
&mut cell_bound,
&mut word_bound,
&mut frame_bound,
&mut cost_bound,
UBOUNDED_MAX,
UBOUNDED_MAX,
dag,
type_dag,
len,
)
.into_result()?;
result.cost_bound = cost_bound;
if test_up_to <= TestUpTo::ComputeCostUnbounded {
return Ok(result);
}
analyseBounds(
&mut cell_bound,
&mut word_bound,
&mut frame_bound,
&mut cost_bound,
cell_bound,
cost_bound,
dag,
type_dag,
len,
)
.into_result()?;
if test_up_to <= TestUpTo::ComputeCostBounded {
return Ok(result);
}
if 0 < cell_bound {
let res = analyseBounds(
&mut cell_bound,
&mut word_bound,
&mut frame_bound,
&mut cost_bound,
cell_bound - 1,
cost_bound,
dag,
type_dag,
len,
)
.into_result()
.expect_err("should fail");
assert_eq!(res, SimplicityErr::ExecMemory);
}
if test_up_to <= TestUpTo::CheckCellCount {
return Ok(result);
}
if 0 < cost_bound {
let res = analyseBounds(
&mut cell_bound,
&mut word_bound,
&mut frame_bound,
&mut cost_bound,
cell_bound,
cost_bound - 1,
dag,
type_dag,
len,
)
.into_result()
.expect_err("should fail");
assert_eq!(res, SimplicityErr::ExecBudget);
}
if test_up_to <= TestUpTo::CheckBudget {
return Ok(result);
}
if (*dag.offset(len as isize - 1)).aux_types.types[0] != 0
|| (*dag.offset(len as isize - 1)).aux_types.types[1] != 0
{
return Err(SimplicityErr::TypeInferenceNotProgram);
}
if test_up_to <= TestUpTo::CheckOneOne {
return Ok(result);
}
result.eval_result = evalTCOProgram(dag, type_dag, len, ptr::null(), ptr::null());
}
Ok(result)
}
pub fn parse_root(ptr: &[u32; 8]) -> [u8; 32] {
let mut a = [0u8; 32];
for i in 0..8 {
let x = ptr[i];
a[i * 4] = (x >> 24) as u8;
a[i * 4 + 1] = (x >> 16) as u8;
a[i * 4 + 2] = (x >> 8) as u8;
a[i * 4 + 3] = x as u8;
}
a
}
pub struct TestData {
pub cmr: [u8; 32],
pub amr: [u8; 32],
pub imr: [u8; 32],
pub prog: Vec<u8>,
pub cost: ubounded,
}
#[cfg(feature = "test-utils")]
mod test_data {
use super::*;
use std::slice;
pub fn schnorr0_test_data() -> TestData {
unsafe {
TestData {
cmr: parse_root(&ffi::schnorr0_cmr),
amr: parse_root(&ffi::schnorr0_amr),
imr: parse_root(&ffi::schnorr0_imr),
prog: slice::from_raw_parts(ffi::schnorr0.as_ptr(), ffi::sizeof_schnorr0).into(),
cost: ffi::schnorr0_cost,
}
}
}
pub fn schnorr6_test_data() -> TestData {
unsafe {
TestData {
cmr: parse_root(&ffi::schnorr6_cmr),
amr: parse_root(&ffi::schnorr6_amr),
imr: parse_root(&ffi::schnorr6_imr),
prog: slice::from_raw_parts(ffi::schnorr6.as_ptr(), ffi::sizeof_schnorr6).into(),
cost: ffi::schnorr6_cost,
}
}
}
pub fn ctx8_pruned_test_data() -> TestData {
unsafe {
TestData {
cmr: parse_root(&ffi::ctx8Pruned_cmr),
amr: parse_root(&ffi::ctx8Pruned_amr),
imr: parse_root(&ffi::ctx8Pruned_imr),
prog: slice::from_raw_parts(ffi::ctx8Pruned.as_ptr(), ffi::sizeof_ctx8Pruned)
.into(),
cost: ffi::ctx8Pruned_cost,
}
}
}
pub fn ctx8_unpruned_test_data() -> TestData {
unsafe {
TestData {
cmr: parse_root(&ffi::ctx8Unpruned_cmr),
amr: parse_root(&ffi::ctx8Unpruned_amr),
imr: parse_root(&ffi::ctx8Unpruned_imr),
prog: slice::from_raw_parts(ffi::ctx8Unpruned.as_ptr(), ffi::sizeof_ctx8Unpruned)
.into(),
cost: ffi::ctx8Unpruned_cost,
}
}
}
}
#[cfg(feature = "test-utils")]
pub use test_data::*;
#[cfg(all(test, feature = "test-utils"))]
mod tests {
use super::*;
#[test]
fn ctx8_pruned() {
unsafe {
assert_eq!(ffi::sizeof_ctx8Pruned, ffi::ctx8Pruned.len());
run_test(
&ffi::ctx8Pruned,
&ffi::ctx8Pruned_amr,
&ffi::ctx8Pruned_cmr,
&ffi::ctx8Pruned_imr,
ffi::ctx8Pruned_cost,
);
}
}
#[test]
fn ctx8_unpruned() {
unsafe {
assert_eq!(ffi::sizeof_ctx8Unpruned, ffi::ctx8Unpruned.len());
run_test_fail(
&ffi::ctx8Unpruned,
SimplicityErr::AntiDoS,
&ffi::ctx8Unpruned_amr,
&ffi::ctx8Unpruned_cmr,
&ffi::ctx8Unpruned_imr,
ffi::ctx8Unpruned_cost,
);
}
}
#[test]
fn schnorr0() {
unsafe {
assert_eq!(ffi::sizeof_schnorr0, ffi::schnorr0.len());
run_test(
&ffi::schnorr0,
&ffi::schnorr0_amr,
&ffi::schnorr0_cmr,
&ffi::schnorr0_imr,
ffi::schnorr0_cost,
);
}
}
#[test]
fn schnorr6() {
unsafe {
assert_eq!(ffi::sizeof_schnorr6, ffi::schnorr6.len());
run_test_fail(
&ffi::schnorr6,
SimplicityErr::ExecJet,
&ffi::schnorr6_amr,
&ffi::schnorr6_cmr,
&ffi::schnorr6_imr,
ffi::schnorr6_cost,
);
}
}
}