pub mod array;
pub mod ast;
pub mod compile;
pub mod debug;
#[cfg(feature = "docs")]
pub mod docs;
pub mod driver;
pub mod dummy_env;
pub mod error;
pub mod jet;
pub mod lexer;
pub mod named;
pub mod num;
pub mod parse;
pub mod pattern;
pub mod resolution;
#[cfg(feature = "serde")]
mod serde;
pub mod str;
#[cfg(test)]
pub mod test_utils;
pub mod tracker;
pub mod types;
pub mod value;
mod witness;
use std::sync::Arc;
use simplicity::jet::elements::ElementsEnv;
use simplicity::{jet::Elements, CommitNode, RedeemNode};
pub extern crate either;
pub extern crate simplicity;
pub use simplicity::elements;
use crate::debug::DebugSymbols;
use crate::driver::DependencyGraph;
use crate::error::{ErrorCollector, WithContent, WithSource as _};
use crate::parse::ParseFromStrWithErrors;
use crate::resolution::{DependencyMap, SourceFile};
pub use crate::types::ResolvedType;
pub use crate::value::Value;
pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TemplateProgram {
simfony: ast::Program,
file: Arc<str>,
}
impl TemplateProgram {
pub fn new_with_dep(
source: SourceFile,
dependency_map: &DependencyMap,
) -> Result<Self, String> {
let mut error_handler = ErrorCollector::new();
let parsed_program =
parse::Program::parse_from_str_with_errors(source.clone(), &mut error_handler)
.ok_or_else(|| error_handler.to_string())?;
let driver_program: driver::Program = if dependency_map.is_empty() {
driver::Program::from_parse(&parsed_program, source.content(), &mut error_handler)
.ok_or_else(|| error_handler.to_string())?
} else {
let graph = DependencyGraph::new(
source.clone(),
Arc::from(dependency_map.clone()),
&parsed_program,
&mut error_handler,
)?
.ok_or_else(|| error_handler.to_string())?;
graph
.linearize_and_build(&mut error_handler)?
.ok_or_else(|| error_handler.to_string())?
};
let ast_program = ast::Program::analyze(&driver_program).with_source(source.clone())?;
Ok(Self {
simfony: ast_program,
file: source.content(),
})
}
pub fn new<Str: Into<Arc<str>>>(s: Str) -> Result<Self, String> {
let file = s.into();
let source = SourceFile::anonymous(file.clone());
let mut error_handler = ErrorCollector::new();
let parse_program = parse::Program::parse_from_str_with_errors(source, &mut error_handler);
let driver_program = if let Some(parse_program) = parse_program {
driver::Program::from_parse(&parse_program, file.clone(), &mut error_handler)
} else {
None
};
if let Some(program) = driver_program {
let ast_program = ast::Program::analyze(&program).with_content(Arc::clone(&file))?;
Ok(Self {
simfony: ast_program,
file,
})
} else {
Err(ErrorCollector::to_string(&error_handler))?
}
}
pub fn parameters(&self) -> &Parameters {
self.simfony.parameters()
}
pub fn witness_types(&self) -> &WitnessTypes {
self.simfony.witness_types()
}
pub fn instantiate(
&self,
arguments: Arguments,
include_debug_symbols: bool,
) -> Result<CompiledProgram, String> {
arguments
.is_consistent(self.simfony.parameters())
.map_err(|error| error.to_string())?;
let commit = self
.simfony
.compile(arguments, include_debug_symbols)
.with_content(Arc::clone(&self.file))?;
Ok(CompiledProgram {
debug_symbols: self.simfony.debug_symbols(self.file.as_ref()),
simplicity: commit,
witness_types: self.simfony.witness_types().shallow_clone(),
parameter_types: self.simfony.parameters().shallow_clone(),
})
}
pub fn generate_abi_meta(&self) -> Result<AbiMeta, String> {
Ok(AbiMeta {
witness_types: self.simfony.witness_types().shallow_clone(),
param_types: self.parameters().shallow_clone(),
})
}
}
#[derive(Clone, Debug)]
pub struct CompiledProgram {
simplicity: Arc<named::CommitNode<Elements>>,
witness_types: WitnessTypes,
debug_symbols: DebugSymbols,
parameter_types: Parameters,
}
impl CompiledProgram {
pub fn new_with_dep(
source: SourceFile,
dependency_map: &DependencyMap,
arguments: Arguments,
include_debug_symbols: bool,
) -> Result<Self, String> {
TemplateProgram::new_with_dep(source, dependency_map)
.and_then(|template| template.instantiate(arguments, include_debug_symbols))
}
pub fn new<Str: Into<Arc<str>>>(
s: Str,
arguments: Arguments,
include_debug_symbols: bool,
) -> Result<Self, String> {
TemplateProgram::new(s)
.and_then(|template| template.instantiate(arguments, include_debug_symbols))
}
pub fn debug_symbols(&self) -> &DebugSymbols {
&self.debug_symbols
}
pub fn commit(&self) -> Arc<CommitNode<Elements>> {
named::forget_names(&self.simplicity)
}
pub fn satisfy(&self, witness_values: WitnessValues) -> Result<SatisfiedProgram, String> {
self.satisfy_with_env(witness_values, None)
}
pub fn satisfy_with_env(
&self,
witness_values: WitnessValues,
env: Option<&ElementsEnv<Arc<elements::Transaction>>>,
) -> Result<SatisfiedProgram, String> {
witness_values
.is_consistent(&self.witness_types)
.map_err(|e| e.to_string())?;
let mut simplicity_redeem = named::populate_witnesses(&self.simplicity, witness_values)?;
if let Some(env) = env {
simplicity_redeem = simplicity_redeem.prune(env).map_err(|e| e.to_string())?;
}
Ok(SatisfiedProgram {
simplicity: simplicity_redeem,
debug_symbols: self.debug_symbols.clone(),
})
}
pub fn generate_abi_meta(&self) -> Result<AbiMeta, String> {
Ok(AbiMeta {
witness_types: self.witness_types.shallow_clone(),
param_types: self.parameter_types.shallow_clone(),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AbiMeta {
pub witness_types: WitnessTypes,
pub param_types: Parameters,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SatisfiedProgram {
simplicity: Arc<RedeemNode<Elements>>,
debug_symbols: DebugSymbols,
}
impl SatisfiedProgram {
pub fn new<Str: Into<Arc<str>>>(
s: Str,
arguments: Arguments,
witness_values: WitnessValues,
include_debug_symbols: bool,
) -> Result<Self, String> {
let compiled = CompiledProgram::new(s, arguments, include_debug_symbols)?;
compiled.satisfy(witness_values)
}
pub fn redeem(&self) -> &Arc<RedeemNode<Elements>> {
&self.simplicity
}
pub fn debug_symbols(&self) -> &DebugSymbols {
&self.debug_symbols
}
}
#[macro_export]
macro_rules! impl_eq_hash {
($ty: ident; $($member: ident),*) => {
impl PartialEq for $ty {
fn eq(&self, other: &Self) -> bool {
true $(&& self.$member() == other.$member())*
}
}
impl Eq for $ty {}
impl std::hash::Hash for $ty {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
$(self.$member().hash(state);)*
}
}
};
($ty:ident < $($gen:ident),+ > ; $($member:ident),*) => {
impl<$($gen),+> PartialEq for $ty<$($gen),+>
where
$($gen: PartialEq,)+
{
fn eq(&self, other: &Self) -> bool {
true $(&& self.$member() == other.$member())*
}
}
impl<$($gen),+> Eq for $ty<$($gen),+>
where
$($gen: Eq,)+
{}
impl<$($gen),+> std::hash::Hash for $ty<$($gen),+>
where
$($gen: std::hash::Hash,)+
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
$(self.$member().hash(state);)*
}
}
};
}
#[cfg(feature = "arbitrary")]
trait ArbitraryRec: Sized {
fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result<Self>;
}
#[cfg(feature = "arbitrary")]
pub trait ArbitraryOfType: Sized {
type Type;
fn arbitrary_of_type(
u: &mut arbitrary::Unstructured,
ty: &Self::Type,
) -> arbitrary::Result<Self>;
}
#[cfg(test)]
pub(crate) mod tests {
use crate::parse::ParseFromStr;
use crate::resolution::CanonPath;
use base64::display::Base64Display;
use base64::engine::general_purpose::STANDARD;
use simplicity::BitMachine;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use crate::*;
pub(crate) struct TestCase<T> {
program: T,
lock_time: elements::LockTime,
sequence: elements::Sequence,
include_fee_output: bool,
}
impl TestCase<TemplateProgram> {
pub fn template_file<P: AsRef<Path>>(program_file_path: P) -> Self {
let program_text = std::fs::read_to_string(program_file_path).unwrap();
Self::template_text(Cow::Owned(program_text))
}
pub fn template_deps(prog_path: &Path, dependency_map: &DependencyMap) -> Self {
let program_text = std::fs::read_to_string(prog_path).unwrap();
let source = SourceFile::new(prog_path, Arc::from(program_text));
let program = match TemplateProgram::new_with_dep(source, dependency_map) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
Self {
program,
lock_time: elements::LockTime::ZERO,
sequence: elements::Sequence::MAX,
include_fee_output: false,
}
}
pub fn template_text(program_text: Cow<str>) -> Self {
let program = match TemplateProgram::new(program_text.as_ref()) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
Self {
program,
lock_time: elements::LockTime::ZERO,
sequence: elements::Sequence::MAX,
include_fee_output: false,
}
}
#[cfg(feature = "serde")]
pub fn with_argument_file<P: AsRef<Path>>(
self,
arguments_file_path: P,
) -> TestCase<CompiledProgram> {
let arguments_text = std::fs::read_to_string(arguments_file_path).unwrap();
let arguments = match serde_json::from_str::<Arguments>(&arguments_text) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
self.with_arguments(arguments)
}
pub fn with_arguments(self, arguments: Arguments) -> TestCase<CompiledProgram> {
let program = match self.program.instantiate(arguments, true) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
TestCase {
program,
lock_time: self.lock_time,
sequence: self.sequence,
include_fee_output: self.include_fee_output,
}
}
}
impl TestCase<CompiledProgram> {
pub fn program_file<P: AsRef<Path>>(program_file_path: P) -> Self {
TestCase::<TemplateProgram>::template_file(program_file_path)
.with_arguments(Arguments::default())
}
pub fn program_text(program_text: Cow<str>) -> Self {
TestCase::<TemplateProgram>::template_text(program_text)
.with_arguments(Arguments::default())
}
pub fn program_file_with_deps<P, I, K>(prog_path: P, dependencies: I) -> Self
where
P: AsRef<Path>,
I: IntoIterator<Item = (P, K, P)>,
K: Into<String>,
{
let mut dependency_map = DependencyMap::new();
for (context, alias, target) in dependencies {
let context = CanonPath::canonicalize(context.as_ref()).unwrap();
let target = CanonPath::canonicalize(target.as_ref()).unwrap();
dependency_map
.insert(context, alias.into(), target)
.unwrap();
}
TestCase::<TemplateProgram>::template_deps(prog_path.as_ref(), &dependency_map)
.with_arguments(Arguments::default())
}
#[cfg(feature = "serde")]
pub fn with_witness_file<P: AsRef<Path>>(
self,
witness_file_path: P,
) -> TestCase<SatisfiedProgram> {
let witness_text = std::fs::read_to_string(witness_file_path).unwrap();
let witness_values = match serde_json::from_str::<WitnessValues>(&witness_text) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
self.with_witness_values(witness_values)
}
pub fn with_witness_values(
self,
witness_values: WitnessValues,
) -> TestCase<SatisfiedProgram> {
let program = match self.program.satisfy(witness_values) {
Ok(x) => x,
Err(error) => panic!("{error}"),
};
TestCase {
program,
lock_time: self.lock_time,
sequence: self.sequence,
include_fee_output: self.include_fee_output,
}
}
pub fn get_encoding(self) -> String {
let program_bytes = self.program.commit().to_vec_without_witness();
Base64Display::new(&program_bytes, &STANDARD).to_string()
}
}
impl<T> TestCase<T> {
#[allow(dead_code)]
pub fn with_lock_time(mut self, height: u32) -> Self {
let height = elements::locktime::Height::from_consensus(height).unwrap();
self.lock_time = elements::LockTime::Blocks(height);
if self.sequence.is_final() {
self.sequence = elements::Sequence::ENABLE_LOCKTIME_NO_RBF;
}
self
}
#[allow(dead_code)]
pub fn with_sequence(mut self, distance: u16) -> Self {
self.sequence = elements::Sequence::from_height(distance);
self
}
#[allow(dead_code)]
pub fn print_sighash_all(self) -> Self {
let env = dummy_env::dummy_with(self.lock_time, self.sequence, self.include_fee_output);
dbg!(env.c_tx_env().sighash_all());
self
}
}
impl TestCase<SatisfiedProgram> {
#[allow(dead_code)]
pub fn print_encoding(self) -> Self {
let (program_bytes, witness_bytes) = self.program.redeem().to_vec_with_witness();
println!(
"Program:\n{}",
Base64Display::new(&program_bytes, &STANDARD)
);
println!(
"Witness:\n{}",
Base64Display::new(&witness_bytes, &STANDARD)
);
self
}
fn run(self) -> Result<(), simplicity::bit_machine::ExecutionError> {
let env = dummy_env::dummy_with(self.lock_time, self.sequence, self.include_fee_output);
let pruned = self.program.redeem().prune(&env)?;
let mut mac = BitMachine::for_program(&pruned)
.expect("program should be within reasonable bounds");
mac.exec(&pruned, &env).map(|_| ())
}
pub fn assert_run_success(self) {
match self.run() {
Ok(()) => {}
Err(error) => panic!("Unexpected error: {error}"),
}
}
pub fn get_encoding_with_witness(self) -> (String, String) {
let (program_bytes, witness_bytes) = self.program.redeem().to_vec_with_witness();
(
Base64Display::new(&program_bytes, &STANDARD).to_string(),
Base64Display::new(&witness_bytes, &STANDARD).to_string(),
)
}
}
pub(crate) fn run_dependency_test(root_path: &str, lib_alias: &str) {
let root_path = PathBuf::from(root_path);
let lib_path = root_path.join(lib_alias);
let main_path = root_path.join("main.simf");
TestCase::program_file_with_deps(
&main_path,
[
(&root_path, lib_alias, &lib_path),
(&lib_path, lib_alias, &lib_path),
],
)
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
pub(crate) fn run_multidep_test(root_path: &str, deps: &[(&str, &str, &str)]) {
let root_path = PathBuf::from(root_path);
let main_path = root_path.join("main.simf");
let mapped_deps: Vec<(PathBuf, &str, PathBuf)> = deps
.iter()
.map(|(ctx, alias, target)| {
let ctx_path = if *ctx == "." {
root_path.clone()
} else {
root_path.join(ctx)
};
let target_path = root_path.join(target);
(ctx_path, *alias, target_path)
})
.collect();
let ref_deps = mapped_deps.iter().map(|(c, a, t)| (c, *a, t));
TestCase::program_file_with_deps(&main_path, ref_deps)
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn single_dep() {
run_dependency_test("./examples/single_dep", "temp");
}
#[test]
fn simple_multidep() {
run_multidep_test(
"./examples/simple_multidep",
&[(".", "math", "math"), (".", "crypto", "crypto")],
);
}
#[test]
fn multiple_deps() {
run_multidep_test(
"./examples/multiple_deps",
&[
(".", "merkle", "merkle"),
(".", "base_math", "math"),
("merkle", "math", "math"),
],
);
}
#[test]
fn cat() {
TestCase::program_file("./examples/cat.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn ctv() {
TestCase::program_file("./examples/ctv.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn regression_153() {
TestCase::program_file("./examples/array_fold_2n.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn pattern_matching() {
TestCase::program_file("./examples/pattern_matching.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_non_interactive_fee_bump() {
let mut t = TestCase::program_file("./examples/non_interactive_fee_bump.simf")
.with_witness_file("./examples/non_interactive_fee_bump.wit");
t.sequence = elements::Sequence::ENABLE_LOCKTIME_NO_RBF;
t.lock_time = elements::LockTime::from_time(1734967235 + 600).unwrap();
t.include_fee_output = true;
t.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn escrow_with_delay_timeout() {
TestCase::program_file("./examples/escrow_with_delay.simf")
.with_sequence(1000)
.print_sighash_all()
.with_witness_file("./examples/escrow_with_delay.timeout.wit")
.assert_run_success();
}
#[test]
fn hash_loop() {
TestCase::program_file("./examples/hash_loop.simf")
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn hodl_vault() {
TestCase::program_file("./examples/hodl_vault.simf")
.with_lock_time(1000)
.print_sighash_all()
.with_witness_file("./examples/hodl_vault.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn htlc_complete() {
TestCase::program_file("./examples/htlc.simf")
.print_sighash_all()
.with_witness_file("./examples/htlc.complete.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn last_will_inherit() {
TestCase::program_file("./examples/last_will.simf")
.with_sequence(25920)
.print_sighash_all()
.with_witness_file("./examples/last_will.inherit.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn p2ms() {
TestCase::program_file("./examples/p2ms.simf")
.print_sighash_all()
.with_witness_file("./examples/p2ms.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn p2pk() {
TestCase::template_file("./examples/p2pk.simf")
.with_argument_file("./examples/p2pk.args")
.print_sighash_all()
.with_witness_file("./examples/p2pk.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn p2pkh() {
TestCase::program_file("./examples/p2pkh.simf")
.print_sighash_all()
.with_witness_file("./examples/p2pkh.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn presigned_vault_complete() {
TestCase::program_file("./examples/presigned_vault.simf")
.with_sequence(1000)
.print_sighash_all()
.with_witness_file("./examples/presigned_vault.complete.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_all_anyonecanpay() {
TestCase::program_file("./examples/sighash_all_anyonecanpay.simf")
.with_witness_file("./examples/sighash_all_anyonecanpay.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_all_anyprevout() {
TestCase::program_file("./examples/sighash_all_anyprevout.simf")
.with_witness_file("./examples/sighash_all_anyprevout.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_all_anyprevoutanyscript() {
TestCase::program_file("./examples/sighash_all_anyprevoutanyscript.simf")
.with_witness_file("./examples/sighash_all_anyprevoutanyscript.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_none() {
TestCase::program_file("./examples/sighash_none.simf")
.with_witness_file("./examples/sighash_none.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn sighash_single() {
TestCase::program_file("./examples/sighash_single.simf")
.with_witness_file("./examples/sighash_single.wit")
.assert_run_success();
}
#[test]
#[cfg(feature = "serde")]
fn transfer_with_timeout_transfer() {
TestCase::program_file("./examples/transfer_with_timeout.simf")
.print_sighash_all()
.with_witness_file("./examples/transfer_with_timeout.transfer.wit")
.assert_run_success();
}
#[test]
fn redefined_variable() {
let prog_text = r#"fn main() {
let beefbabe: (u16, u16) = (0xbeef, 0xbabe);
let beefbabe: u32 = <(u16, u16)>::into(beefbabe);
}
"#;
TestCase::program_text(Cow::Borrowed(prog_text))
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn empty_function_body_nonempty_return() {
let prog_text = r#"fn my_true() -> bool {
// function body is empty, although function must return `bool`
}
fn main() {
assert!(my_true());
}
"#;
match SatisfiedProgram::new(
prog_text,
Arguments::default(),
WitnessValues::default(),
false,
) {
Ok(_) => panic!("Accepted faulty program"),
Err(error) => {
assert!(
error.contains("Expected expression of type `bool`, found type `()`"),
"Unexpected error: {error}",
);
}
}
}
#[test]
fn fuzz_regression_2() {
parse::Program::parse_from_str("fn dbggscas(h: bool, asyxhaaaa: a) {\nfalse}\n\n").unwrap();
}
#[test]
fn fuzz_slow_unit_1() {
parse::Program::parse_from_str("fn fnnfn(MMet:(((sssss,((((((sssss,ssssss,ss,((((((sssss,ss,((((((sssss,ssssss,ss,((((((sssss,ssssss,((((((sssss,sssssssss,(((((((sssss,sssssssss,(((((ssss,((((((sssss,sssssssss,(((((((sssss,ssss,((((((sssss,ss,((((((sssss,ssssss,ss,((((((sssss,ssssss,((((((sssss,sssssssss,(((((((sssss,sssssssss,(((((ssss,((((((sssss,sssssssss,(((((((sssss,sssssssssssss,(((((((((((u|(").unwrap_err();
}
#[test]
fn type_alias() {
let prog_text = r#"type MyAlias = u32;
fn main() {
let x: MyAlias = 32;
assert!(jet::eq_32(x, 32));
}"#;
TestCase::program_text(Cow::Borrowed(prog_text))
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[test]
fn type_error_regression() {
let prog_text = r#"fn main() {
let (a, b): (u32, u32) = (0, 1);
assert!(jet::eq_32(a, 0));
let (c, d): (u32, u32) = (2, 3);
assert!(jet::eq_32(c, 2));
assert!(jet::eq_32(d, 3));
}"#;
TestCase::program_text(Cow::Borrowed(prog_text))
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
#[cfg(feature = "serde")]
mod regression {
use super::TestCase;
#[derive(serde::Deserialize)]
struct Program {
program: String,
witness: Option<String>,
}
fn regression_test(name: &str) {
let program = serde_json::from_str::<Program>(
std::fs::read_to_string(format!("./test-data/{}.json", name))
.unwrap()
.as_str(),
)
.unwrap();
let test_case = TestCase::program_file(format!("./examples/{}.simf", name));
match program.witness {
Some(wit) => {
let (new_program, new_witness) = test_case
.with_witness_file(format!("./examples/{}.wit", name))
.get_encoding_with_witness();
assert_eq!(
program.program, new_program,
"Byte code of programs should be the same"
);
assert_eq!(
wit, new_witness,
"Byte code of witnesses should be the same"
);
}
None => {
let new_program = test_case.get_encoding();
assert_eq!(
program.program, new_program,
"Byte code of programs should be the same"
)
}
}
}
#[test]
fn array_fold_2n_regression() {
regression_test("array_fold_2n");
}
#[test]
fn array_fold_regression() {
regression_test("array_fold");
}
#[test]
fn cat_regression() {
regression_test("cat");
}
#[test]
fn ctv_regression() {
regression_test("ctv");
}
#[test]
fn escrow_with_delay_regression() {
regression_test("escrow_with_delay");
}
#[test]
fn hash_loop_regression() {
regression_test("hash_loop");
}
#[test]
fn hodl_vault_regression() {
regression_test("hodl_vault");
}
#[test]
fn htlc_regression() {
regression_test("htlc");
}
#[test]
fn last_will_regression() {
regression_test("last_will");
}
#[test]
fn non_interactive_fee_bump_regression() {
regression_test("non_interactive_fee_bump");
}
#[test]
fn p2ms_regression() {
regression_test("p2ms");
}
#[test]
fn p2pkh_regression() {
regression_test("p2pkh");
}
#[test]
fn presigned_vault_regression() {
regression_test("presigned_vault");
}
#[test]
fn reveal_collision_regression() {
regression_test("reveal_collision");
}
#[test]
fn reveal_fix_point_regression() {
regression_test("reveal_fix_point");
}
#[test]
fn sighash_all_anyonecanpay_regression() {
regression_test("sighash_all_anyonecanpay");
}
#[test]
fn sighash_all_anyprevoutanyscript_regression() {
regression_test("sighash_all_anyprevoutanyscript");
}
#[test]
fn sighash_all_anyprevout_regression() {
regression_test("sighash_all_anyprevout");
}
#[test]
fn sighash_none_regression() {
regression_test("sighash_none");
}
#[test]
fn sighash_single_regression() {
regression_test("sighash_single");
}
#[test]
fn transfer_with_timeout_regression() {
regression_test("transfer_with_timeout");
}
}
}
#[cfg(test)]
mod error_tests {
use std::path::Path;
use super::*;
use crate::resolution::tests::canon;
use crate::resolution::CanonPath;
use crate::test_utils::TempWorkspace;
fn dependency_map(root_dir: &Path, drp: &str, lib_dir: &Path) -> DependencyMap {
let mut dependency_map = DependencyMap::new();
let context = CanonPath::canonicalize(root_dir).unwrap();
let target = CanonPath::canonicalize(lib_dir).unwrap();
dependency_map.insert(context, drp.into(), target).unwrap();
dependency_map
}
fn source_file(path: &Path) -> SourceFile {
let content = std::fs::read_to_string(path).expect("Failed to read test file");
SourceFile::new(path, Arc::from(content))
}
#[test]
#[ignore = "TODO: Bug in Error Handler. Expected to be fixed in a future update to correctly point to dependency source files."]
fn dependency_ast_errors_use_dependency_source_file() {
let ws = TempWorkspace::new("dependency_ast_error_source");
let root_dir = ws.create_dir("workspace");
let lib_dir = ws.create_dir("workspace/lib");
let main_path = ws.create_file(
"workspace/main.simf",
"use lib::bad::f;\nfn main() { f(); }\n",
);
let bad_path = ws.create_file(
"workspace/lib/bad.simf",
"pub fn f() { let x: u32 = true; }\n",
);
let dependencies = dependency_map(&root_dir, "lib", &lib_dir);
let err = TemplateProgram::new_with_dep(source_file(&main_path), &dependencies)
.expect_err("dependency body has a type error");
let dependency_source = canon(&bad_path).as_path().display().to_string();
assert!(
err.contains(&dependency_source),
"expected diagnostic to point at dependency source {dependency_source}, got:\n{err}"
);
}
#[test]
fn omitted_context_dependency_applies_inside_dependency_files() {
let ws = TempWorkspace::new("omitted_context_dependency");
let lib_dir = ws.create_dir("workspace/lib");
let main_path = ws.create_file(
"workspace/main.simf",
"use lib::nested::two;\nfn main() { assert!(jet::eq_32(two(), 2)); }\n",
);
ws.create_file(
"workspace/lib/nested.simf",
"use lib::base::one;\npub fn two() -> u32 {\n let (_, out): (bool, u32) = jet::add_32(one(), 1);\n out\n}\n",
);
ws.create_file("workspace/lib/base.simf", "pub fn one() -> u32 { 1 }\n");
let dependencies = dependency_map(&main_path, "lib", &lib_dir);
let _err = TemplateProgram::new_with_dep(source_file(&main_path), &dependencies)
.expect_err("omitted-context dependencies");
}
#[test]
fn missing_mapped_module_is_reported_as_file_not_found() {
let ws = TempWorkspace::new("missing_mapped_module");
let root_dir = ws.create_dir("workspace");
let lib_dir = ws.create_dir("workspace/lib");
let main_path = ws.create_file(
"workspace/main.simf",
"use lib::missing::Thing;\nfn main() {}\n",
);
let dependencies = dependency_map(&root_dir, "lib", &lib_dir);
let err = TemplateProgram::new_with_dep(source_file(&main_path), &dependencies)
.expect_err("missing imported module should fail");
assert!(
err.contains("missing.simf"),
"diagnostic should mention the missing module path, got:\n{err}"
);
}
}
#[cfg(test)]
mod functional_tests {
use crate::tests::{run_dependency_test, run_multidep_test};
const VALID_TESTS_DIR: &str = "./functional-tests/valid-test-cases";
const ERROR_TESTS_DIR: &str = "./functional-tests/error-test-cases";
#[test]
fn module_simple() {
run_dependency_test(format!("{}/module-simple", VALID_TESTS_DIR).as_str(), "lib");
}
#[test]
fn diamond_dependency_resolution() {
run_dependency_test(
format!("{}/diamond-dependency-resolution", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn deep_reexport_chain() {
run_dependency_test(
format!("{}/deep-reexport-chain", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn leaky_signature() {
run_dependency_test(
format!("{}/leaky-signature", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn reexport_diamond() {
run_dependency_test(
format!("{}/reexport-diamond", VALID_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
fn multi_lib_facade_resolution() {
run_multidep_test(
format!("{}/multi-lib-facade", VALID_TESTS_DIR).as_str(),
&[
(".", "api", "api"),
("crypto", "math", "math"),
("api", "crypto", "crypto"),
("api", "math", "math"),
],
);
}
#[test]
fn interleaved_waterfall() {
run_multidep_test(
format!("{}/interleaved-waterfall", VALID_TESTS_DIR).as_str(),
&[
(".", "orch", "orch"),
("orch", "db", "db"),
("orch", "auth", "auth"),
("orch", "types", "types"),
("db", "types", "types"),
("auth", "types", "types"),
("auth", "db", "db"),
],
);
}
#[test]
#[should_panic(expected = "Circular dependency detected:")]
fn cyclic_dependency_error() {
run_dependency_test(
format!("{}/cyclic-dependency", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
#[should_panic(expected = "No such file or directory")]
fn file_not_found_error() {
run_dependency_test(
format!("{}/file-not-found", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
#[should_panic(expected = "No such file or directory")]
fn lib_not_found_error() {
run_dependency_test(format!("{}/lib-not-found", ERROR_TESTS_DIR).as_str(), "lib");
}
#[test]
#[should_panic(expected = "Item `SecretType` is private")]
fn private_type_visibility_error() {
run_dependency_test(
format!("{}/private-visibility", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
#[should_panic(expected = "The alias `add` was defined multiple times")]
fn name_collision_error() {
run_dependency_test(
format!("{}/name-collision", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
#[test]
#[should_panic(expected = "Type alias `A` was defined multiple times")]
fn type_alias_duplication_error() {
run_dependency_test(
format!("{}/type-alias-duplication", ERROR_TESTS_DIR).as_str(),
"lib",
);
}
}