pub type ProgNode = Arc<named::ConstructNode>;
pub mod array;
pub mod ast;
pub mod compile;
pub mod debug;
pub mod dummy_env;
pub mod error;
pub mod jet;
pub mod named;
pub mod num;
pub mod parse;
pub mod pattern;
#[cfg(feature = "serde")]
mod serde;
pub mod str;
pub mod types;
pub mod value;
mod witness;
use std::sync::Arc;
use simplicity::{jet::Elements, CommitNode, RedeemNode};
pub extern crate either;
pub extern crate simplicity;
pub use simplicity::elements;
use crate::debug::DebugSymbols;
use crate::error::WithFile;
use crate::parse::ParseFromStr;
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<Str: Into<Arc<str>>>(s: Str) -> Result<Self, String> {
let file = s.into();
let parse_program = parse::Program::parse_from_str(&file)?;
let ast_program = ast::Program::analyze(&parse_program).with_file(Arc::clone(&file))?;
Ok(Self {
simfony: ast_program,
file,
})
}
pub fn parameters(&self) -> &Parameters {
self.simfony.parameters()
}
pub fn instantiate(&self, arguments: Arguments) -> Result<CompiledProgram, String> {
arguments
.is_consistent(self.simfony.parameters())
.map_err(|error| error.to_string())?;
Ok(CompiledProgram {
debug_symbols: self.simfony.debug_symbols(self.file.as_ref()),
simplicity: self
.simfony
.compile(arguments)
.with_file(Arc::clone(&self.file))?,
witness_types: self.simfony.witness_types().shallow_clone(),
})
}
}
#[derive(Clone, Debug)]
pub struct CompiledProgram {
simplicity: ProgNode,
witness_types: WitnessTypes,
debug_symbols: DebugSymbols,
}
impl Default for CompiledProgram {
fn default() -> Self {
use simplicity::node::CoreConstructible;
Self {
simplicity: ProgNode::unit(&simplicity::types::Context::new()),
witness_types: WitnessTypes::default(),
debug_symbols: DebugSymbols::default(),
}
}
}
impl CompiledProgram {
pub fn new<Str: Into<Arc<str>>>(s: Str, arguments: Arguments) -> Result<Self, String> {
TemplateProgram::new(s).and_then(|template| template.instantiate(arguments))
}
pub fn debug_symbols(&self) -> &DebugSymbols {
&self.debug_symbols
}
pub fn commit(&self) -> Arc<CommitNode<Elements>> {
named::to_commit_node(&self.simplicity).expect("Compiled Simfony program has type 1 -> 1")
}
pub fn satisfy(&self, witness_values: WitnessValues) -> Result<SatisfiedProgram, String> {
witness_values
.is_consistent(&self.witness_types)
.map_err(|e| e.to_string())?;
let simplicity_witness = named::to_witness_node(&self.simplicity, witness_values);
let simplicity_redeem = simplicity_witness
.finalize_unpruned()
.map_err(|e| e.to_string())?;
Ok(SatisfiedProgram {
simplicity: simplicity_redeem,
debug_symbols: self.debug_symbols.clone(),
})
}
}
#[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,
) -> Result<Self, String> {
let compiled = CompiledProgram::new(s, arguments)?;
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);)*
}
}
};
}
#[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)]
mod tests {
use base64::display::Base64Display;
use base64::engine::general_purpose::STANDARD;
use simplicity::BitMachine;
use std::borrow::Cow;
use std::path::Path;
use 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_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) {
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())
}
#[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,
}
}
}
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().encode_to_vec();
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}"),
}
}
}
#[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]
#[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()) {
Ok(_) => panic!("Accepted faulty program"),
Err(error) => {
if !error.contains("Expected expression of type `bool`, found type `()`") {
panic!("Unexpected error: {error}")
}
}
}
}
#[test]
fn fuzz_regression_2() {
parse::Program::parse_from_str("fn dbggscas(h: bool, asyxhaaaa: a) {\nfalse}\n\n").unwrap();
}
#[test]
#[ignore]
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();
}
}