From 2b21dcebd292d725ba98ef3bec312f5f61c8ebeb Mon Sep 17 00:00:00 2001 From: arvidn Date: Tue, 14 Feb 2023 22:11:06 +0100 Subject: [PATCH] fix edge cases in assert_before parsing. Specifically, assert_before a negative value is an automatic fail. assert_before a value exceeding the max is an automatic pass --- src/gen/condition_sanitizers.rs | 256 +++++++++++++++++++++----------- src/gen/conditions.rs | 170 +++++++++++++++------ 2 files changed, 292 insertions(+), 134 deletions(-) diff --git a/src/gen/condition_sanitizers.rs b/src/gen/condition_sanitizers.rs index 354f2896a..1bbddc171 100644 --- a/src/gen/condition_sanitizers.rs +++ b/src/gen/condition_sanitizers.rs @@ -61,6 +61,38 @@ pub fn parse_seconds(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result Result, ValidationErr> { + // heights are not allowed to exceed 2^32. i.e. 4 bytes + match sanitize_uint(a, n, 4, code) { + Err(ValidationErr(n, ErrorCode::NegativeAmount)) => Err(ValidationErr(n, code)), + Err(ValidationErr(_, ErrorCode::AmountExceedsMaximum)) => Ok(None), + Err(r) => Err(r), + Ok(r) => Ok(Some(u64_from_bytes(r) as u32)), + } +} + +// negative seconds are a failure, exceeding the max limit is no-op. +// this is used for parsing assert_before_seconds conditions. +pub fn parse_positive_seconds( + a: &Allocator, + n: NodePtr, + code: ErrorCode, +) -> Result, ValidationErr> { + // seconds are not allowed to exceed 2^64. i.e. 8 bytes + match sanitize_uint(a, n, 8, code) { + Err(ValidationErr(n, ErrorCode::NegativeAmount)) => Err(ValidationErr(n, code)), + Err(ValidationErr(_, ErrorCode::AmountExceedsMaximum)) => Ok(None), + Err(r) => Err(r), + Ok(r) => Ok(Some(u64_from_bytes(r))), + } +} + pub fn sanitize_announce_msg( a: &Allocator, n: NodePtr, @@ -75,6 +107,9 @@ pub fn sanitize_announce_msg( } } +#[cfg(test)] +use rstest::rstest; + #[cfg(test)] fn zero_vec(len: usize) -> Vec { let mut ret = Vec::::new(); @@ -227,120 +262,167 @@ fn test_sanitize_create_coin_amount() { } #[cfg(test)] -fn height_tester(buf: &[u8]) -> Result { +fn height_tester(buf: &[u8]) -> Result, ValidationErr> { let mut a = Allocator::new(); let n = a.new_atom(buf).unwrap(); - - parse_height(&mut a, n, ErrorCode::AssertHeightAbsolute) + Ok(Some( + parse_height(&mut a, n, ErrorCode::AssertHeightAbsolute)? as u64, + )) } -#[test] -fn test_parse_height() { - // negative heights can be ignored - assert_eq!(height_tester(&[0x80]), Ok(0)); - assert_eq!(height_tester(&[0xff]), Ok(0)); - assert_eq!(height_tester(&[0xff, 0]), Ok(0)); - - // leading zeros are somtimes necessary to make values positive - assert_eq!(height_tester(&[0, 0xff]), Ok(0xff)); - // but are stripped when they are redundant - assert_eq!( - height_tester(&[0, 0, 0, 0xff]).unwrap_err().1, - ErrorCode::AssertHeightAbsolute - ); - assert_eq!( - height_tester(&[0, 0, 0, 0x80]).unwrap_err().1, - ErrorCode::AssertHeightAbsolute - ); - assert_eq!( - height_tester(&[0, 0, 0, 0x7f]).unwrap_err().1, - ErrorCode::AssertHeightAbsolute - ); - assert_eq!( - height_tester(&[0, 0, 0]).unwrap_err().1, - ErrorCode::AssertHeightAbsolute - ); - assert_eq!( - height_tester(&[0]).unwrap_err().1, - ErrorCode::AssertHeightAbsolute - ); - - // heights aren't allowed to be > 2^32 (i.e. 5 bytes) - assert_eq!( - height_tester(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff]) - .unwrap_err() - .1, - ErrorCode::AssertHeightAbsolute - ); - - // this is small enough though - assert_eq!(height_tester(&[0, 0xff, 0xff, 0xff, 0xff]), Ok(0xffffffff)); +#[cfg(test)] +fn seconds_tester(buf: &[u8]) -> Result, ValidationErr> { + let mut a = Allocator::new(); + let n = a.new_atom(buf).unwrap(); + Ok(Some(parse_seconds( + &mut a, + n, + ErrorCode::AssertSecondsAbsolute, + )?)) +} +#[cfg(test)] +fn positive_height_tester(buf: &[u8]) -> Result, ValidationErr> { let mut a = Allocator::new(); - let pair = a.new_pair(a.null(), a.null()).unwrap(); - assert_eq!( - parse_height(&mut a, pair, ErrorCode::AssertHeightAbsolute), - Err(ValidationErr(pair, ErrorCode::AssertHeightAbsolute)) - ); + let n = a.new_atom(buf).unwrap(); + Ok(parse_positive_height(&mut a, n, ErrorCode::AssertBeforeHeightAbsolute)?.map(|v| v as u64)) } #[cfg(test)] -fn seconds_tester(buf: &[u8]) -> Result { +fn positive_seconds_tester(buf: &[u8]) -> Result, ValidationErr> { let mut a = Allocator::new(); let n = a.new_atom(buf).unwrap(); + parse_positive_seconds(&mut a, n, ErrorCode::AssertBeforeSecondsAbsolute) +} - parse_seconds(&mut a, n, ErrorCode::AssertSecondsAbsolute) +#[cfg(test)] +#[rstest] +// == parse_height +#[case(height_tester, &[0x80], Some(0))] +// negative values are no-ops +#[case(height_tester, &[0xff], Some(0))] +#[case(height_tester, &[0xff, 0], Some(0))] +// leading zeros are sometimes necessary to make values positive +#[case(height_tester, &[0, 0xff], Some(0xff))] +// this is small enough +#[case(height_tester, &[0, 0xff, 0xff, 0xff, 0xff], Some(0xffffffff))] +// == parse_seconds +#[case(seconds_tester, &[0x80], Some(0))] +// negative values are no-ops +#[case(seconds_tester, &[0xff], Some(0))] +#[case(seconds_tester, &[0xff, 0], Some(0))] +// leading zeros are sometimes necessary to make values positive +#[case(seconds_tester, &[0, 0xff], Some(0xff))] +// this is small enough +#[case(seconds_tester, &[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], Some(0xffffffffffffffff))] +// == parse_positive_height +// leading zeros are sometimes necessary to make values positive +#[case(positive_height_tester, &[0, 0xff], Some(0xff))] +// this is small enough +#[case(positive_height_tester, &[0, 0xff, 0xff, 0xff, 0xff], Some(0xffffffff))] +// positive heights are allowed to be > 2^32 (i.e. 5 bytes). it's a no-op +#[case(positive_height_tester, &[0x01, 0xff, 0xff, 0xff, 0xff], None)] +// == parse_positive_seconds +// leading zeros are sometimes necessary to make values positive +#[case(positive_seconds_tester, &[0, 0xff], Some(0xff))] +// this is small enough +#[case(positive_seconds_tester, &[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], Some(0xffffffffffffffff))] +// before-seconds are allowed to be > 2^64 (i.e. 9 bytes) +#[case(positive_seconds_tester, &[0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], None)] +fn test_parse_ok( + #[case] fun: impl Fn(&[u8]) -> Result, ValidationErr>, + #[case] buf: &[u8], + #[case] expected: Option, +) { + println!("test case: {:?} expect: {:?}", buf, expected); + // negative heights can be ignored + assert_eq!(fun(buf).unwrap(), expected); } -#[test] -fn test_parse_seconds() { - // negative seconds can be ignored - assert_eq!(seconds_tester(&[0x80]), Ok(0)); - assert_eq!(seconds_tester(&[0xff]), Ok(0)); - assert_eq!(seconds_tester(&[0xff, 0]), Ok(0)); +#[cfg(test)] +#[rstest] +// == parse_height +#[case(height_tester, &[0, 0, 0, 0xff], ErrorCode::AssertHeightAbsolute)] +#[case(height_tester, &[0, 0, 0, 0x80], ErrorCode::AssertHeightAbsolute)] +#[case(height_tester, &[0, 0, 0, 0x7f], ErrorCode::AssertHeightAbsolute)] +#[case(height_tester, &[0, 0, 0], ErrorCode::AssertHeightAbsolute)] +#[case(height_tester, &[0], ErrorCode::AssertHeightAbsolute)] +// heights aren't allowed to be > 2^32 (i.e. 5 bytes) +#[case(height_tester, &[0x01, 0xff, 0xff, 0xff, 0xff], ErrorCode::AssertHeightAbsolute)] +// == parse_seconds +#[case(seconds_tester, &[0, 0, 0, 0xff], ErrorCode::AssertSecondsAbsolute)] +#[case(seconds_tester, &[0, 0, 0, 0x80], ErrorCode::AssertSecondsAbsolute)] +#[case(seconds_tester, &[0, 0, 0, 0x7f], ErrorCode::AssertSecondsAbsolute)] +#[case(seconds_tester, &[0, 0, 0], ErrorCode::AssertSecondsAbsolute)] +#[case(seconds_tester, &[0], ErrorCode::AssertSecondsAbsolute)] +// positive seconds are allowed to be > 2^64 (i.e. 9 bytes) +#[case(seconds_tester, &[0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], ErrorCode::AssertSecondsAbsolute)] +// == parse_positive_height +#[case(positive_height_tester, &[0, 0, 0, 0xff], ErrorCode::AssertBeforeHeightAbsolute)] +#[case(positive_height_tester, &[0, 0, 0, 0x80], ErrorCode::AssertBeforeHeightAbsolute)] +#[case(positive_height_tester, &[0, 0, 0, 0x7f], ErrorCode::AssertBeforeHeightAbsolute)] +#[case(positive_height_tester, &[0, 0, 0], ErrorCode::AssertBeforeHeightAbsolute)] +#[case(positive_height_tester, &[0], ErrorCode::AssertBeforeHeightAbsolute)] +// negative values are failures +#[case(positive_height_tester, &[0x80], ErrorCode::AssertBeforeHeightAbsolute)] +#[case(positive_height_tester, &[0xff], ErrorCode::AssertBeforeHeightAbsolute)] +#[case(positive_height_tester, &[0xff, 0], ErrorCode::AssertBeforeHeightAbsolute)] +// == parse_positive_seconds +#[case(positive_seconds_tester, &[0, 0, 0, 0xff], ErrorCode::AssertBeforeSecondsAbsolute)] +#[case(positive_seconds_tester, &[0, 0, 0, 0x80], ErrorCode::AssertBeforeSecondsAbsolute)] +#[case(positive_seconds_tester, &[0, 0, 0, 0x7f], ErrorCode::AssertBeforeSecondsAbsolute)] +#[case(positive_seconds_tester, &[0, 0, 0], ErrorCode::AssertBeforeSecondsAbsolute)] +#[case(positive_seconds_tester, &[0], ErrorCode::AssertBeforeSecondsAbsolute)] +// negative values are failures +#[case(positive_seconds_tester, &[0x80], ErrorCode::AssertBeforeSecondsAbsolute)] +#[case(positive_seconds_tester, &[0xff], ErrorCode::AssertBeforeSecondsAbsolute)] +#[case(positive_seconds_tester, &[0xff, 0], ErrorCode::AssertBeforeSecondsAbsolute)] +fn test_parse_fail( + #[case] fun: impl Fn(&[u8]) -> Result, ValidationErr>, + #[case] buf: &[u8], + #[case] expected: ErrorCode, +) { + println!("test case: {:?} expect: {:?}", buf, expected); + // negative heights can be ignored + assert_eq!(fun(buf).unwrap_err().1, expected); +} - // leading zeros are somtimes necessary to make values positive - assert_eq!(seconds_tester(&[0, 0xff]), Ok(0xff)); - // but are stripped when they are redundant - assert_eq!( - seconds_tester(&[0, 0, 0, 0xff]).unwrap_err().1, - ErrorCode::AssertSecondsAbsolute - ); - assert_eq!( - seconds_tester(&[0, 0, 0, 0x80]).unwrap_err().1, - ErrorCode::AssertSecondsAbsolute - ); - assert_eq!( - seconds_tester(&[0, 0, 0, 0x7f]).unwrap_err().1, - ErrorCode::AssertSecondsAbsolute - ); - assert_eq!( - seconds_tester(&[0, 0, 0]).unwrap_err().1, - ErrorCode::AssertSecondsAbsolute - ); +#[test] +fn test_parse_height_pair() { + let mut a = Allocator::new(); + let pair = a.new_pair(a.null(), a.null()).unwrap(); assert_eq!( - seconds_tester(&[0]).unwrap_err().1, - ErrorCode::AssertSecondsAbsolute + parse_height(&mut a, pair, ErrorCode::AssertHeightAbsolute), + Err(ValidationErr(pair, ErrorCode::AssertHeightAbsolute)) ); +} - // seconds aren't allowed to be > 2^64 (i.e. 9 bytes) +#[test] +fn test_parse_seconds_pair() { + let mut a = Allocator::new(); + let pair = a.new_pair(a.null(), a.null()).unwrap(); assert_eq!( - seconds_tester(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],) - .unwrap_err() - .1, - ErrorCode::AssertSecondsAbsolute + parse_seconds(&mut a, pair, ErrorCode::AssertSecondsAbsolute), + Err(ValidationErr(pair, ErrorCode::AssertSecondsAbsolute)) ); +} - // this is small enough though +#[test] +fn test_parse_positive_height_pair() { + let mut a = Allocator::new(); + let pair = a.new_pair(a.null(), a.null()).unwrap(); assert_eq!( - seconds_tester(&[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), - Ok(0xffffffffffffffff) + parse_positive_height(&mut a, pair, ErrorCode::AssertHeightAbsolute), + Err(ValidationErr(pair, ErrorCode::AssertHeightAbsolute)) ); +} +#[test] +fn test_parse_positive_seconds_pair() { let mut a = Allocator::new(); let pair = a.new_pair(a.null(), a.null()).unwrap(); assert_eq!( - parse_seconds(&mut a, pair, ErrorCode::AssertSecondsAbsolute), + parse_positive_seconds(&mut a, pair, ErrorCode::AssertSecondsAbsolute), Err(ValidationErr(pair, ErrorCode::AssertSecondsAbsolute)) ); } diff --git a/src/gen/conditions.rs b/src/gen/conditions.rs index b26c2a023..5000f493d 100644 --- a/src/gen/conditions.rs +++ b/src/gen/conditions.rs @@ -1,7 +1,7 @@ use super::coin_id::compute_coin_id; use super::condition_sanitizers::{ - parse_amount, parse_create_coin_amount, parse_height, parse_seconds, sanitize_announce_msg, - sanitize_hash, + parse_amount, parse_create_coin_amount, parse_height, parse_positive_height, + parse_positive_seconds, parse_seconds, sanitize_announce_msg, sanitize_hash, }; use super::opcodes::{ parse_opcode, ConditionOpcode, AGG_SIG_COST, AGG_SIG_ME, AGG_SIG_UNSAFE, ALWAYS_TRUE, @@ -276,23 +276,31 @@ pub fn parse_args( } ASSERT_BEFORE_SECONDS_RELATIVE => { maybe_check_args_terminator(a, c, flags)?; - let seconds = parse_seconds(a, first(a, c)?, ErrorCode::AssertBeforeSecondsRelative)?; - Ok(Condition::AssertBeforeSecondsRelative(seconds)) + match parse_positive_seconds(a, first(a, c)?, ErrorCode::AssertBeforeSecondsRelative)? { + None => Ok(Condition::Skip), + Some(seconds) => Ok(Condition::AssertBeforeSecondsRelative(seconds)), + } } ASSERT_BEFORE_SECONDS_ABSOLUTE => { maybe_check_args_terminator(a, c, flags)?; - let seconds = parse_seconds(a, first(a, c)?, ErrorCode::AssertBeforeSecondsAbsolute)?; - Ok(Condition::AssertBeforeSecondsAbsolute(seconds)) + match parse_positive_seconds(a, first(a, c)?, ErrorCode::AssertBeforeSecondsAbsolute)? { + None => Ok(Condition::Skip), + Some(seconds) => Ok(Condition::AssertBeforeSecondsAbsolute(seconds)), + } } ASSERT_BEFORE_HEIGHT_RELATIVE => { maybe_check_args_terminator(a, c, flags)?; - let height = parse_height(a, first(a, c)?, ErrorCode::AssertBeforeHeightRelative)?; - Ok(Condition::AssertBeforeHeightRelative(height)) + match parse_positive_height(a, first(a, c)?, ErrorCode::AssertBeforeHeightRelative)? { + None => Ok(Condition::Skip), + Some(height) => Ok(Condition::AssertBeforeHeightRelative(height)), + } } ASSERT_BEFORE_HEIGHT_ABSOLUTE => { maybe_check_args_terminator(a, c, flags)?; - let height = parse_height(a, first(a, c)?, ErrorCode::AssertBeforeHeightAbsolute)?; - Ok(Condition::AssertBeforeHeightAbsolute(height)) + match parse_positive_height(a, first(a, c)?, ErrorCode::AssertBeforeHeightAbsolute)? { + None => Ok(Condition::Skip), + Some(height) => Ok(Condition::AssertBeforeHeightAbsolute(height)), + } } ALWAYS_TRUE => { // this condition is always true, we always ignore arguments @@ -1325,52 +1333,20 @@ fn test_extra_arg( test(&conds, &spend); } -#[cfg(test)] -#[rstest] -#[case(ASSERT_SECONDS_ABSOLUTE, ErrorCode::AssertSecondsAbsolute)] -#[case(ASSERT_SECONDS_RELATIVE, ErrorCode::AssertSecondsRelative)] -#[case(ASSERT_BEFORE_SECONDS_ABSOLUTE, ErrorCode::AssertBeforeSecondsAbsolute)] -#[case(ASSERT_BEFORE_SECONDS_RELATIVE, ErrorCode::AssertBeforeSecondsRelative)] -fn test_seconds_exceed_max(#[case] condition: ConditionOpcode, #[case] expected_error: ErrorCode) { - // ASSERT_SECONDS_RELATIVE - assert_eq!( - cond_test(&format!( - "((({{h1}} ({{h2}} (123 ((({} (0x010000000000000000 )))))", - condition as u8 - )) - .unwrap_err() - .1, - expected_error - ); -} - -#[cfg(test)] -#[rstest] -#[case(ASSERT_HEIGHT_ABSOLUTE, ErrorCode::AssertHeightAbsolute)] -#[case(ASSERT_HEIGHT_RELATIVE, ErrorCode::AssertHeightRelative)] -#[case(ASSERT_BEFORE_HEIGHT_ABSOLUTE, ErrorCode::AssertBeforeHeightAbsolute)] -#[case(ASSERT_BEFORE_HEIGHT_RELATIVE, ErrorCode::AssertBeforeHeightRelative)] -fn test_height_exceed_max(#[case] condition: ConditionOpcode, #[case] expected_error: ErrorCode) { - assert_eq!( - cond_test(&format!( - "((({{h1}} ({{h2}} (123 ((({} (0x0100000000 )))))", - condition as u8 - )) - .unwrap_err() - .1, - expected_error - ); -} - #[cfg(test)] #[rstest] #[case(ASSERT_SECONDS_ABSOLUTE, "104", |c: &SpendBundleConditions, _: &Spend| assert_eq!(c.seconds_absolute, 104))] +#[case(ASSERT_SECONDS_ABSOLUTE, "-1", |c: &SpendBundleConditions, _: &Spend| assert_eq!(c.seconds_absolute, 0))] #[case(ASSERT_SECONDS_RELATIVE, "101", |_: &SpendBundleConditions, s: &Spend| assert_eq!(s.seconds_relative, 101))] +#[case(ASSERT_SECONDS_RELATIVE, "-1", |_: &SpendBundleConditions, s: &Spend| assert_eq!(s.seconds_relative, 0))] #[case(ASSERT_HEIGHT_RELATIVE, "101", |_: &SpendBundleConditions, s: &Spend| assert_eq!(s.height_relative, Some(101)))] +#[case(ASSERT_HEIGHT_RELATIVE, "-1", |_: &SpendBundleConditions, s: &Spend| assert_eq!(s.height_relative, None))] #[case(ASSERT_HEIGHT_ABSOLUTE, "100", |c: &SpendBundleConditions, _: &Spend| assert_eq!(c.height_absolute, 100))] +#[case(ASSERT_HEIGHT_ABSOLUTE, "-1", |c: &SpendBundleConditions, _: &Spend| assert_eq!(c.height_absolute, 0))] #[case(ASSERT_BEFORE_SECONDS_ABSOLUTE, "104", |c: &SpendBundleConditions, _: &Spend| assert_eq!(c.before_seconds_absolute, Some(104)))] #[case(ASSERT_BEFORE_SECONDS_RELATIVE, "101", |_: &SpendBundleConditions, s: &Spend| assert_eq!(s.before_seconds_relative, Some(101)))] #[case(ASSERT_BEFORE_HEIGHT_RELATIVE, "101", |_: &SpendBundleConditions, s: &Spend| assert_eq!(s.before_height_relative, Some(101)))] +#[case(ASSERT_BEFORE_HEIGHT_RELATIVE, "0", |_: &SpendBundleConditions, s: &Spend| assert_eq!(s.before_height_relative, Some(0)))] #[case(ASSERT_BEFORE_HEIGHT_ABSOLUTE, "100", |c: &SpendBundleConditions, _: &Spend| assert_eq!(c.before_height_absolute, Some(100)))] #[case(RESERVE_FEE, "100", |c: &SpendBundleConditions, _: &Spend| assert_eq!(c.reserve_fee, 100))] #[case(ASSERT_MY_AMOUNT, "123", |_: &SpendBundleConditions, _: &Spend| {})] @@ -1400,6 +1376,106 @@ fn test_single_condition( test(&conds, &spend); } +#[cfg(test)] +#[rstest] +#[case(ASSERT_BEFORE_SECONDS_ABSOLUTE, "0x010000000000000000")] +#[case(ASSERT_BEFORE_SECONDS_RELATIVE, "0x010000000000000000")] +#[case(ASSERT_BEFORE_HEIGHT_ABSOLUTE, "0x0100000000")] +#[case(ASSERT_BEFORE_HEIGHT_RELATIVE, "0x0100000000")] +#[case(ASSERT_SECONDS_ABSOLUTE, "-1")] +#[case(ASSERT_SECONDS_RELATIVE, "-1")] +#[case(ASSERT_HEIGHT_ABSOLUTE, "-1")] +#[case(ASSERT_HEIGHT_RELATIVE, "-1")] +fn test_single_condition_no_op(#[case] condition: ConditionOpcode, #[case] value: &str) { + let (_, conds) = cond_test(&format!( + "((({{h1}} ({{h2}} (123 ((({} ({} )))))", + condition as u8, value + )) + .unwrap(); + + assert_eq!(conds.height_absolute, 0); + assert_eq!(conds.seconds_absolute, 0); + assert_eq!(conds.before_height_absolute, None); + assert_eq!(conds.before_seconds_absolute, None); + let spend = &conds.spends[0]; + assert_eq!(spend.before_height_relative, None); + assert_eq!(spend.before_seconds_relative, None); + assert_eq!(spend.height_relative, None); + assert_eq!(spend.seconds_relative, 0); +} + +#[cfg(test)] +#[rstest] +#[case( + ASSERT_SECONDS_ABSOLUTE, + "0x010000000000000000", + ErrorCode::AssertSecondsAbsolute +)] +#[case( + ASSERT_SECONDS_RELATIVE, + "0x010000000000000000", + ErrorCode::AssertSecondsRelative +)] +#[case( + ASSERT_HEIGHT_ABSOLUTE, + "0x0100000000", + ErrorCode::AssertHeightAbsolute +)] +#[case( + ASSERT_HEIGHT_RELATIVE, + "0x0100000000", + ErrorCode::AssertHeightRelative +)] +#[case( + ASSERT_BEFORE_SECONDS_ABSOLUTE, + "-1", + ErrorCode::AssertBeforeSecondsAbsolute +)] +#[case( + ASSERT_BEFORE_SECONDS_ABSOLUTE, + "0", + ErrorCode::ImpossibleSecondsAbsoluteConstraints +)] +#[case( + ASSERT_BEFORE_SECONDS_RELATIVE, + "-1", + ErrorCode::AssertBeforeSecondsRelative +)] +#[case( + ASSERT_BEFORE_SECONDS_RELATIVE, + "0", + ErrorCode::ImpossibleSecondsRelativeConstraints +)] +#[case( + ASSERT_BEFORE_HEIGHT_ABSOLUTE, + "-1", + ErrorCode::AssertBeforeHeightAbsolute +)] +#[case( + ASSERT_BEFORE_HEIGHT_ABSOLUTE, + "0", + ErrorCode::ImpossibleHeightAbsoluteConstraints +)] +#[case( + ASSERT_BEFORE_HEIGHT_RELATIVE, + "-1", + ErrorCode::AssertBeforeHeightRelative +)] +fn test_single_condition_failure( + #[case] condition: ConditionOpcode, + #[case] arg: &str, + #[case] expected_error: ErrorCode, +) { + let err = cond_test(&format!( + "((({{h1}} ({{h2}} (123 ((({} ({} )))))", + condition as u8, arg + )) + .unwrap_err() + .1; + + assert_eq!(err, expected_error); +} + // this test ensures that the ASSERT_BEFORE_ condition codes are not available // unless the ENABLE_ASSERT_BEFORE flag is set #[cfg(test)]