diff --git a/simf/lib/op_return.simf b/simf/lib/op_return.simf new file mode 100644 index 0000000..2f71087 --- /dev/null +++ b/simf/lib/op_return.simf @@ -0,0 +1,12 @@ +/// Returns true if the specified output is an OP_RETURN (null data) output +pub fn is_output_op_return(output_index: u32) -> bool { + match jet::output_null_datum(output_index, 0) { + Some(entry: Option>>) => true, + None => false, + } +} + +/// Asserts that the specified output is an OP_RETURN (null data) output +pub fn assert_output_is_op_return(output_index: u32) { + assert!(is_output_op_return(output_index)); +} diff --git a/simf/op_return_test.simf b/simf/op_return_test.simf new file mode 100644 index 0000000..d847d8e --- /dev/null +++ b/simf/op_return_test.simf @@ -0,0 +1,12 @@ +use crate::lib::op_return::{is_output_op_return, assert_output_is_op_return}; +use crate::helper::{if_test_this_function, assert_bool}; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let index: u32 = witness::INDEX; + let expected: bool = witness::EXPECTED; + + match if_test_this_function(0, fn_idx) { true => { assert_bool(is_output_op_return(index), expected); }, false => (), }; + match if_test_this_function(1, fn_idx) { true => { assert_output_is_op_return(index); }, false => (), }; +} diff --git a/tests/common/core.rs b/tests/common/core.rs index 9b4d8e2..ada5e42 100644 --- a/tests/common/core.rs +++ b/tests/common/core.rs @@ -4,7 +4,9 @@ use simplex::program::{Program, WitnessTrait}; use simplex::simplicityhl::elements::Script; -use simplex::transaction::{FinalTransaction, PartialInput, ProgramInput, RequiredSignature}; +use simplex::transaction::{ + FinalTransaction, PartialInput, PartialOutput, ProgramInput, RequiredSignature, +}; #[derive(Clone, Copy)] pub enum Expect { @@ -41,13 +43,14 @@ pub fn fund( Ok(script) } -/// Spend the funded UTXO with `witness`. Returns the broadcast result. -pub fn spend( +/// Construct the funded UTXO with `witness`. +pub fn construct_final_tx( context: &simplex::TestContext, program: &impl AsRef, script: &Script, witness: W, -) -> anyhow::Result + data: Option<&[u8]>, +) -> anyhow::Result where W: WitnessTrait + 'static, { @@ -62,22 +65,34 @@ where RequiredSignature::None, ); - Ok(context.get_default_signer().broadcast(&ft)?.to_string()) + if let Some(data) = data { + ft.add_output(PartialOutput::new_metadata(data)) + }; + + Ok(ft) } -/// Fund + spend + assert the outcome. -pub fn run( +/// Spend the funded UTXO with `witness`. Return the broadcast result. +pub fn spend( context: &simplex::TestContext, - program: impl AsRef, + program: &impl AsRef, + script: &Script, witness: W, - expect: Expect, -) -> anyhow::Result<()> + data: Option<&[u8]>, +) -> anyhow::Result where W: WitnessTrait + 'static, { - let script = fund(context, &program)?; - let result = spend(context, &program, &script, witness); + let ft = construct_final_tx(context, program, script, witness, data)?; + + Ok(context.get_default_signer().broadcast(&ft)?.to_string()) +} +/// Assert that the test result is as expected. +pub fn assert_error_msg( + result: Result, + expect: Expect, +) -> anyhow::Result<()> { match expect.error_message() { None => { result?; @@ -88,7 +103,41 @@ where .to_string(); assert!(err.contains(expected)); } - } + }; Ok(()) } + +/// Fund + spend + assert the outcome. +pub fn run( + context: &simplex::TestContext, + program: impl AsRef, + witness: W, + expect: Expect, +) -> anyhow::Result<()> +where + W: WitnessTrait + 'static, +{ + let script = fund(context, &program)?; + let result = spend(context, &program, &script, witness, None); + + assert_error_msg(result, expect) +} + +/// Fund + spend + assert the outcome. +/// Tx has OP_RETURN data metadata output +pub fn run_with_op_return( + context: &simplex::TestContext, + program: impl AsRef, + witness: W, + expect: Expect, + data: &[u8], +) -> anyhow::Result<()> +where + W: WitnessTrait + 'static, +{ + let script = fund(context, &program)?; + let result = spend(context, &program, &script, witness, Some(data)); + + assert_error_msg(result, expect) +} diff --git a/tests/op_return_test.rs b/tests/op_return_test.rs new file mode 100644 index 0000000..917bd49 --- /dev/null +++ b/tests/op_return_test.rs @@ -0,0 +1,130 @@ +mod common; + +use rand::Rng; + +use crate::common::core::{run, run_with_op_return}; +use common::core::Expect; + +use simplicityhl_std::artifacts::op_return_test::OpReturnTestProgram; +use simplicityhl_std::artifacts::op_return_test::derived_op_return_test::{ + OpReturnTestArguments, OpReturnTestWitness, +}; + +enum FunctionToTest { + IsOpReturn, + AssertOutputIsOpReturn, +} + +#[inline] +fn op(o: FunctionToTest) -> u8 { + o as u8 +} + +const DEFAULT_BOOL: bool = false; +const DEFAULT_DATA: &[u8; 1] = &[1]; + +fn program() -> OpReturnTestProgram { + OpReturnTestProgram::new(OpReturnTestArguments {}) +} + +fn build_witness(function: u8, index: u32, expected: bool) -> OpReturnTestWitness { + OpReturnTestWitness { + function_index: function, + index, + expected, + } +} + +mod op_return_tests { + use super::*; + + #[simplex::test] + fn is_output_op_return_true(context: simplex::TestContext) -> anyhow::Result<()> { + let index = 0; + + run_with_op_return( + &context, + program(), + build_witness(op(FunctionToTest::IsOpReturn), index, true), + Expect::Ok, + DEFAULT_DATA, + ) + } + + #[simplex::test] + fn is_output_op_return_empty_index_false(context: simplex::TestContext) -> anyhow::Result<()> { + let index = rand::thread_rng().gen_range(1..=u32::MAX); + + run_with_op_return( + &context, + program(), + build_witness(op(FunctionToTest::IsOpReturn), index, false), + Expect::Ok, + DEFAULT_DATA, + ) + } + + #[simplex::test] + fn is_output_op_return_false(context: simplex::TestContext) -> anyhow::Result<()> { + let index = 0; + + run( + &context, + program(), + build_witness(op(FunctionToTest::IsOpReturn), index, false), + Expect::Ok, + ) + } + + #[simplex::test] + fn assert_output_is_op_return_pass(context: simplex::TestContext) -> anyhow::Result<()> { + let index = 0; + + run_with_op_return( + &context, + program(), + build_witness( + op(FunctionToTest::AssertOutputIsOpReturn), + index, + DEFAULT_BOOL, + ), + Expect::Ok, + DEFAULT_DATA, + ) + } + + #[simplex::test] + fn assert_output_is_op_return_empty_index_fail( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let index = rand::thread_rng().gen_range(1..=u32::MAX); + + run_with_op_return( + &context, + program(), + build_witness( + op(FunctionToTest::AssertOutputIsOpReturn), + index, + DEFAULT_BOOL, + ), + Expect::AssertFailed, + DEFAULT_DATA, + ) + } + + #[simplex::test] + fn assert_output_is_op_return_fail(context: simplex::TestContext) -> anyhow::Result<()> { + let index = 0; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::AssertOutputIsOpReturn), + index, + DEFAULT_BOOL, + ), + Expect::AssertFailed, + ) + } +}