From 7a1150d2edc2b3663f58246d654534c4140df2a7 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:21:06 +0200 Subject: [PATCH] feat: limit open-ended vectors for covenants (#6497) Description --- Limited open-ended vectors to guard against malicious network messages in: - covenants Motivation and Context --- This is a _defense-in-depth_ exercise. How Has This Been Tested? --- Existing unit tests pass. What process can a PR reviewer use to test or verify this change? --- Code review Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --- base_layer/core/src/covenants/arguments.rs | 16 +++--- base_layer/core/src/covenants/context.rs | 1 + base_layer/core/src/covenants/covenant.rs | 25 +++++---- base_layer/core/src/covenants/decoder.rs | 1 + base_layer/core/src/covenants/encoder.rs | 4 +- base_layer/core/src/covenants/error.rs | 4 ++ base_layer/core/src/covenants/fields.rs | 2 +- .../src/covenants/filters/absolute_height.rs | 6 +-- base_layer/core/src/covenants/filters/and.rs | 2 +- .../core/src/covenants/filters/field_eq.rs | 19 ++++--- .../src/covenants/filters/fields_hashed_eq.rs | 2 +- .../src/covenants/filters/fields_preserved.rs | 3 +- .../core/src/covenants/filters/identity.rs | 2 +- base_layer/core/src/covenants/filters/not.rs | 2 +- base_layer/core/src/covenants/filters/or.rs | 2 +- .../src/covenants/filters/output_hash_eq.rs | 2 +- base_layer/core/src/covenants/filters/xor.rs | 2 +- base_layer/core/src/covenants/macros.rs | 52 +++++++++++-------- base_layer/core/src/covenants/token.rs | 4 +- .../transaction_components/test.rs | 7 +-- .../chain_storage_tests/chain_storage.rs | 2 +- base_layer/wallet_ffi/src/lib.rs | 4 +- 22 files changed, 98 insertions(+), 66 deletions(-) diff --git a/base_layer/core/src/covenants/arguments.rs b/base_layer/core/src/covenants/arguments.rs index faa895fe15..984ead7723 100644 --- a/base_layer/core/src/covenants/arguments.rs +++ b/base_layer/core/src/covenants/arguments.rs @@ -48,6 +48,8 @@ use crate::{ const MAX_COVENANT_ARG_SIZE: usize = 4096; const MAX_BYTES_ARG_SIZE: usize = 4096; +pub(crate) type BytesArg = MaxSizeBytes; + #[derive(Debug, Clone, PartialEq, Eq)] /// Covenant arguments pub enum CovenantArg { @@ -60,7 +62,7 @@ pub enum CovenantArg { Uint(u64), OutputField(OutputField), OutputFields(OutputFields), - Bytes(Vec), + Bytes(BytesArg), } impl CovenantArg { @@ -117,8 +119,8 @@ impl CovenantArg { Ok(CovenantArg::OutputFields(fields)) }, ARG_BYTES => { - let buf = MaxSizeBytes::::deserialize(reader)?; - Ok(CovenantArg::Bytes(buf.into())) + let buf = BytesArg::deserialize(reader)?; + Ok(CovenantArg::Bytes(buf)) }, _ => Err(CovenantDecodeError::UnknownArgByteCode { code }), @@ -224,7 +226,7 @@ impl CovenantArg { require_x_impl!(require_outputfields, OutputFields, "outputfields"); - require_x_impl!(require_bytes, Bytes, "bytes", Vec); + require_x_impl!(require_bytes, Bytes, "bytes", BytesArg); require_x_impl!(require_uint, Uint, "u64", u64); } @@ -278,6 +280,8 @@ mod test { } mod write_to_and_read_from { + use std::convert::TryFrom; + use super::*; fn test_case(argument: CovenantArg, mut data: &[u8]) { @@ -295,11 +299,11 @@ mod test { fn test() { test_case(CovenantArg::Uint(2048), &[ARG_UINT, 0, 8, 0, 0, 0, 0, 0, 0][..]); test_case( - CovenantArg::Covenant(covenant!(identity())), + CovenantArg::Covenant(covenant!(identity()).unwrap()), &[ARG_COVENANT, 0x01, 0x20][..], ); test_case( - CovenantArg::Bytes(vec![0x01, 0x02, 0xaa]), + CovenantArg::Bytes(BytesArg::try_from(vec![0x01, 0x02, 0xaa]).unwrap()), &[ARG_BYTES, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0xaa][..], ); test_case( diff --git a/base_layer/core/src/covenants/context.rs b/base_layer/core/src/covenants/context.rs index 6069bbf809..dfb677b0ba 100644 --- a/base_layer/core/src/covenants/context.rs +++ b/base_layer/core/src/covenants/context.rs @@ -32,6 +32,7 @@ use crate::{ /// The covenant execution context provides a reference to the transaction input being verified, the tokenized covenant /// and other relevant context e.g current block height +#[derive(Debug)] pub struct CovenantContext<'a> { input: &'a TransactionInput, tokens: CovenantTokenCollection, diff --git a/base_layer/core/src/covenants/covenant.rs b/base_layer/core/src/covenants/covenant.rs index e010780f9d..588b1d3b33 100644 --- a/base_layer/core/src/covenants/covenant.rs +++ b/base_layer/core/src/covenants/covenant.rs @@ -27,6 +27,7 @@ use std::{ use borsh::{BorshDeserialize, BorshSerialize}; use integer_encoding::{VarIntReader, VarIntWriter}; +use tari_max_size::MaxSizeVec; use super::decoder::CovenantDecodeError; use crate::{ @@ -45,11 +46,13 @@ use crate::{ const MAX_COVENANT_BYTES: usize = 4096; -#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub(crate) type CovenantTokens = MaxSizeVec; + +#[derive(Debug, Default, Clone, PartialEq, Eq)] /// A covenant allows a UTXO to specify some restrictions on how it is spent in a future transaction. /// See https://rfc.tari.com/RFC-0250_Covenants.html for details. pub struct Covenant { - tokens: Vec, + tokens: CovenantTokens, } impl BorshSerialize for Covenant { @@ -85,7 +88,9 @@ impl BorshDeserialize for Covenant { impl Covenant { pub fn new() -> Self { - Self { tokens: Vec::new() } + Self { + tokens: CovenantTokens::default(), + } } /// Produces a new `Covenant` instance, out of a byte buffer. It errors @@ -110,7 +115,7 @@ impl Covenant { /// Writes a `Covenant` instance byte to a writer. pub(super) fn write_to(&self, writer: &mut W) -> Result<(), io::Error> { - CovenantTokenEncoder::new(self.tokens.as_slice()).write_to(writer) + CovenantTokenEncoder::new(&self.tokens).write_to(writer) } /// Gets the byte lenght of the underlying byte buffer @@ -149,8 +154,8 @@ impl Covenant { } /// Adds a new `CovenantToken` to the current `tokens` vector field. - pub fn push_token(&mut self, token: CovenantToken) { - self.tokens.push(token); + pub fn push_token(&mut self, token: CovenantToken) -> Result<(), CovenantError> { + Ok(self.tokens.push(token)?) } #[cfg(test)] @@ -197,7 +202,7 @@ mod test { let key_manager = create_memory_db_key_manager().unwrap(); let outputs = create_outputs(10, UtxoTestParams::default(), &key_manager).await; let input = create_input(&key_manager).await; - let covenant = covenant!(); + let covenant = covenant!().unwrap(); let num_matching_outputs = covenant.execute(0, &input, &outputs).unwrap(); assert_eq!(num_matching_outputs, 10); } @@ -214,7 +219,8 @@ mod test { let covenant = covenant!(fields_preserved(@fields( @field::features_output_type, @field::features_maturity)) - ); + ) + .unwrap(); let num_matching_outputs = covenant.execute(0, &input, &outputs).unwrap(); assert_eq!(num_matching_outputs, 3); } @@ -231,7 +237,8 @@ mod test { let covenant = covenant!(fields_preserved(@fields( @field::features_output_type, @field::features_maturity)) - ); + ) + .unwrap(); let mut buf = Vec::new(); covenant.serialize(&mut buf).unwrap(); buf.extend_from_slice(&[1, 2, 3]); diff --git a/base_layer/core/src/covenants/decoder.rs b/base_layer/core/src/covenants/decoder.rs index 9a8aef6ec0..9cc63b0ad2 100644 --- a/base_layer/core/src/covenants/decoder.rs +++ b/base_layer/core/src/covenants/decoder.rs @@ -182,6 +182,7 @@ mod test { @fields(@field::commitment), @hash(hash), )) + .unwrap() .write_to(&mut bytes) .unwrap(); let mut buf = bytes.as_slice(); diff --git a/base_layer/core/src/covenants/encoder.rs b/base_layer/core/src/covenants/encoder.rs index 00bca70b3c..8a228f7e2e 100644 --- a/base_layer/core/src/covenants/encoder.rs +++ b/base_layer/core/src/covenants/encoder.rs @@ -75,7 +75,7 @@ mod tests { #[test] fn it_encodes_tokens_correctly() { - let covenant = covenant!(and(identity(), or(identity()))); + let covenant = covenant!(and(identity(), or(identity()))).unwrap(); let encoder = CovenantTokenEncoder::new(covenant.tokens()); let mut buf = Vec::::new(); encoder.write_to(&mut buf).unwrap(); @@ -85,7 +85,7 @@ mod tests { #[test] fn it_encodes_args_correctly() { let dummy = FixedHash::zero(); - let covenant = covenant!(field_eq(@field::features, @hash(dummy))); + let covenant = covenant!(field_eq(@field::features, @hash(dummy))).unwrap(); let encoder = CovenantTokenEncoder::new(covenant.tokens()); let mut buf = Vec::::new(); encoder.write_to(&mut buf).unwrap(); diff --git a/base_layer/core/src/covenants/error.rs b/base_layer/core/src/covenants/error.rs index 147a0a8eca..bca13bcfe8 100644 --- a/base_layer/core/src/covenants/error.rs +++ b/base_layer/core/src/covenants/error.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use tari_max_size::MaxSizeVecError; + #[derive(Debug, thiserror::Error)] pub enum CovenantError { #[error("Reached the end of tokens but another token was expected")] @@ -36,4 +38,6 @@ pub enum CovenantError { RemainingTokens, #[error("Invalid argument for filter {filter}: {details}")] InvalidArgument { filter: &'static str, details: String }, + #[error("Max sized vector error: {0}")] + MaxSizeVecError(#[from] MaxSizeVecError), } diff --git a/base_layer/core/src/covenants/fields.rs b/base_layer/core/src/covenants/fields.rs index a00a5fe4ef..a2e3d1e8b1 100644 --- a/base_layer/core/src/covenants/fields.rs +++ b/base_layer/core/src/covenants/fields.rs @@ -471,7 +471,7 @@ mod test { .is_eq(&output, &PublicKey::default()) .unwrap()); assert!(!OutputField::Covenant - .is_eq(&output, &covenant!(and(identity(), identity()))) + .is_eq(&output, &covenant!(and(identity(), identity())).unwrap()) .unwrap()); assert!(!OutputField::Features .is_eq(&output, &OutputFeatures::default()) diff --git a/base_layer/core/src/covenants/filters/absolute_height.rs b/base_layer/core/src/covenants/filters/absolute_height.rs index 14a2b6be04..e3c3b63d3c 100644 --- a/base_layer/core/src/covenants/filters/absolute_height.rs +++ b/base_layer/core/src/covenants/filters/absolute_height.rs @@ -73,7 +73,7 @@ mod test { #[tokio::test] async fn it_filters_all_out_if_height_not_reached() { let key_manager = create_memory_db_key_manager().unwrap(); - let covenant = covenant!(absolute_height(@uint(100))); + let covenant = covenant!(absolute_height(@uint(100))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test(&covenant, &input, 42, |_| {}, &key_manager).await; @@ -86,7 +86,7 @@ mod test { #[tokio::test] async fn it_filters_all_in_if_height_reached() { let key_manager = create_memory_db_key_manager().unwrap(); - let covenant = covenant!(absolute_height(@uint(100))); + let covenant = covenant!(absolute_height(@uint(100))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test(&covenant, &input, 100, |_| {}, &key_manager).await; @@ -99,7 +99,7 @@ mod test { #[tokio::test] async fn it_filters_all_in_if_height_exceeded() { let key_manager = create_memory_db_key_manager().unwrap(); - let covenant = covenant!(absolute_height(@uint(42))); + let covenant = covenant!(absolute_height(@uint(42))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test(&covenant, &input, 100, |_| {}, &key_manager).await; diff --git a/base_layer/core/src/covenants/filters/and.rs b/base_layer/core/src/covenants/filters/and.rs index f06192ab8e..bdbc57084a 100644 --- a/base_layer/core/src/covenants/filters/and.rs +++ b/base_layer/core/src/covenants/filters/and.rs @@ -55,7 +55,7 @@ mod test { async fn it_filters_outputset_using_intersection() { let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(101)).unwrap(); - let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))); + let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test( &covenant, diff --git a/base_layer/core/src/covenants/filters/field_eq.rs b/base_layer/core/src/covenants/filters/field_eq.rs index 04eb94e3d7..24719877f0 100644 --- a/base_layer/core/src/covenants/filters/field_eq.rs +++ b/base_layer/core/src/covenants/filters/field_eq.rs @@ -91,7 +91,7 @@ mod test { #[tokio::test] async fn it_filters_uint() { let key_manager = create_memory_db_key_manager().unwrap(); - let covenant = covenant!(field_eq(@field::features_maturity, @uint(42))); + let covenant = covenant!(field_eq(@field::features_maturity, @uint(42))).unwrap(); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); // Remove `field_eq` @@ -112,7 +112,8 @@ mod test { let covenant = covenant!(field_eq( @field::sender_offset_public_key, @public_key(pk.clone()) - )); + )) + .unwrap(); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); // Remove `field_eq` @@ -134,7 +135,8 @@ mod test { let covenant = covenant!(field_eq( @field::commitment, @commitment(commitment.clone()) - )); + )) + .unwrap(); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); // Remove `field_eq` @@ -156,7 +158,8 @@ mod test { let covenant = covenant!(field_eq( @field::script, @script(script.clone()) - )); + )) + .unwrap(); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); // Remove `field_eq` @@ -174,8 +177,8 @@ mod test { #[tokio::test] async fn it_filters_covenant() { let key_manager = create_memory_db_key_manager().unwrap(); - let next_cov = covenant!(and(identity(), or(field_eq(@field::features_maturity, @uint(42))))); - let covenant = covenant!(field_eq(@field::covenant, @covenant(next_cov.clone()))); + let next_cov = covenant!(and(identity(), or(field_eq(@field::features_maturity, @uint(42))))).unwrap(); + let covenant = covenant!(field_eq(@field::covenant, @covenant(next_cov.clone()))).unwrap(); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); // Remove `field_eq` @@ -193,7 +196,7 @@ mod test { #[tokio::test] async fn it_filters_output_type() { let key_manager = create_memory_db_key_manager().unwrap(); - let covenant = covenant!(field_eq(@field::features_output_type, @output_type(Coinbase))); + let covenant = covenant!(field_eq(@field::features_output_type, @output_type(Coinbase))).unwrap(); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); // Remove `field_eq` @@ -211,7 +214,7 @@ mod test { #[tokio::test] async fn it_errors_if_field_has_an_incorrect_type() { let key_manager = create_memory_db_key_manager().unwrap(); - let covenant = covenant!(field_eq(@field::features, @uint(42))); + let covenant = covenant!(field_eq(@field::features, @uint(42))).unwrap(); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); // Remove `field_eq` diff --git a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs index d7502ac141..98cd719320 100644 --- a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs +++ b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs @@ -71,7 +71,7 @@ mod test { let mut hasher = Blake2b::::new(); BaseLayerCovenantsDomain::add_domain_separation_tag(&mut hasher, COVENANTS_FIELD_HASHER_LABEL); let hash = hasher.chain(borsh::to_vec(&features).unwrap()).finalize(); - let covenant = covenant!(fields_hashed_eq(@fields(@field::features), @hash(hash.into()))); + let covenant = covenant!(fields_hashed_eq(@fields(@field::features), @hash(hash.into()))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test( &covenant, diff --git a/base_layer/core/src/covenants/filters/fields_preserved.rs b/base_layer/core/src/covenants/filters/fields_preserved.rs index 2d7dc6c92b..65a17eca2f 100644 --- a/base_layer/core/src/covenants/filters/fields_preserved.rs +++ b/base_layer/core/src/covenants/filters/fields_preserved.rs @@ -49,7 +49,8 @@ mod test { #[tokio::test] async fn it_filters_outputs_that_match_input_fields() { - let covenant = covenant!(fields_preserved(@fields(@field::features_maturity, @field::features_output_type))); + let covenant = + covenant!(fields_preserved(@fields(@field::features_maturity, @field::features_output_type))).unwrap(); let key_manager = create_memory_db_key_manager().unwrap(); let mut input = create_input(&key_manager).await; input.set_maturity(42).unwrap(); diff --git a/base_layer/core/src/covenants/filters/identity.rs b/base_layer/core/src/covenants/filters/identity.rs index 13f85394ee..835e995e8a 100644 --- a/base_layer/core/src/covenants/filters/identity.rs +++ b/base_layer/core/src/covenants/filters/identity.rs @@ -45,7 +45,7 @@ mod tests { #[tokio::test] async fn it_returns_the_outputset_unchanged() { let key_manager = create_memory_db_key_manager().unwrap(); - let covenant = covenant!(identity()); + let covenant = covenant!(identity()).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |_| {}, &key_manager).await; let mut output_set = OutputSet::new(&outputs); diff --git a/base_layer/core/src/covenants/filters/not.rs b/base_layer/core/src/covenants/filters/not.rs index b1fd99b61c..c218a73d0c 100644 --- a/base_layer/core/src/covenants/filters/not.rs +++ b/base_layer/core/src/covenants/filters/not.rs @@ -52,7 +52,7 @@ mod test { async fn it_filters_compliment_of_filter() { let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(100)).unwrap(); - let covenant = covenant!(not(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone()))))); + let covenant = covenant!(not(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone()))))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test( &covenant, diff --git a/base_layer/core/src/covenants/filters/or.rs b/base_layer/core/src/covenants/filters/or.rs index 000fdc9e4f..6c2cefc471 100644 --- a/base_layer/core/src/covenants/filters/or.rs +++ b/base_layer/core/src/covenants/filters/or.rs @@ -58,7 +58,7 @@ mod test { async fn it_filters_outputset_using_union() { let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(100)).unwrap(); - let covenant = covenant!(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))); + let covenant = covenant!(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test( &covenant, diff --git a/base_layer/core/src/covenants/filters/output_hash_eq.rs b/base_layer/core/src/covenants/filters/output_hash_eq.rs index 0a4f5542f9..d50c8b6b04 100644 --- a/base_layer/core/src/covenants/filters/output_hash_eq.rs +++ b/base_layer/core/src/covenants/filters/output_hash_eq.rs @@ -57,7 +57,7 @@ mod test { let output_hash = output.hash(); let mut hash = [0u8; 32]; hash.copy_from_slice(output_hash.as_slice()); - let covenant = covenant!(output_hash_eq(@hash(hash.into()))); + let covenant = covenant!(output_hash_eq(@hash(hash.into()))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test( &covenant, diff --git a/base_layer/core/src/covenants/filters/xor.rs b/base_layer/core/src/covenants/filters/xor.rs index 5a9e1e26ac..09fd2c6154 100644 --- a/base_layer/core/src/covenants/filters/xor.rs +++ b/base_layer/core/src/covenants/filters/xor.rs @@ -60,7 +60,7 @@ mod test { async fn it_filters_outputset_using_symmetric_difference() { let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(100)).unwrap(); - let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))); + let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))).unwrap(); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test( &covenant, diff --git a/base_layer/core/src/covenants/macros.rs b/base_layer/core/src/covenants/macros.rs index 79ba9155e1..8ef7392301 100644 --- a/base_layer/core/src/covenants/macros.rs +++ b/base_layer/core/src/covenants/macros.rs @@ -33,24 +33,33 @@ /// /// ```rust,ignore /// // Before height 42, this may only be spent into an output with flag 8 (NON_FUNGIBLE) -/// let covenant = covenant!(or(absolute_height(@uint(42)), field_eq(@field::features_flags, @uint(8)))); +/// let covenant = covenant!(or(absolute_height(@uint(42)), field_eq(@field::features_flags, @uint(8)))).unwrap(); /// covenant.execute(...)?; /// ``` + #[macro_export] macro_rules! covenant { ($token:ident($($args:tt)*)) => {{ let mut covenant = $crate::covenants::Covenant::new(); - $crate::__covenant_inner!(@ { covenant } $token($($args)*)); - covenant + // We declare and use a closure to ensure that the covenant is returned as a Result + let ops = ||{ + $crate::__covenant_inner!(@ { covenant } $token($($args)*),); + Ok::<_, $crate::covenants::CovenantError>(covenant) + }; + ops() }}; ($token:ident()) => {{ let mut covenant = $crate::covenants::Covenant::new(); - $crate::__covenant_inner!(@ { covenant } $token()); - covenant + // We declare and use a closure to ensure that the covenant is returned as a Result + let ops = ||{ + $crate::__covenant_inner!(@ { covenant } $token(),); + Ok::<_, $crate::covenants::CovenantError>(covenant) + }; + ops() }}; - () => { $crate::covenants::Covenant::new() }; + () => { Ok::<_, $crate::covenants::CovenantError>($crate::covenants::Covenant::new()) }; } #[macro_export] @@ -81,16 +90,16 @@ macro_rules! covenant { // // This macro pattern is called a tt-muncher (tee hee) macro_rules! __covenant_inner { - (@ { $covenant:ident }) => {}; + (@ { $covenant:ident }) => { }; // token() (@ { $covenant:ident } $token:ident() $(,)?) => { - $covenant.push_token($crate::covenants::CovenantToken::$token()); + $covenant.push_token($crate::covenants::CovenantToken::$token())? }; // @field::name, ... (@ { $covenant:ident } @field::$field:ident, $($tail:tt)*) => { - $covenant.push_token($crate::covenants::CovenantToken::field($crate::covenants::OutputField::$field())); + $covenant.push_token($crate::covenants::CovenantToken::field($crate::covenants::OutputField::$field()))?; $crate::__covenant_inner!(@ { $covenant } $($tail)*) }; @@ -108,14 +117,14 @@ macro_rules! __covenant_inner { (@ { $covenant:ident } @fields($(@field::$field:ident),+ $(,)?), $($tail:tt)*) => { $covenant.push_token($crate::covenants::CovenantToken::fields(vec![ $($crate::covenants::OutputField::$field()),+ - ])); + ]))?; $crate::__covenant_inner!(@ { $covenant } $($tail)*) }; // @covenant_lit(...), ... (@ { $covenant:ident } @covenant_lit($($inner:tt)*), $($tail:tt)*) => { - let inner = $crate::covenant!($($inner)*); - $covenant.push_token($crate::covenants::CovenantToken::covenant(inner)); + $crate::covenant!($($inner)*)?; + $covenant.push_token($crate::covenants::CovenantToken::covenant(inner))?; $crate::__covenant_inner!(@ { $covenant } $($tail)*) }; @@ -127,13 +136,13 @@ macro_rules! __covenant_inner { // @output_type(expr1), ... (@ { $covenant:ident } @output_type($arg:expr $(,)?), $($tail:tt)*) => { use $crate::transactions::transaction_components::OutputType::*; - $covenant.push_token($crate::covenants::CovenantToken::output_type($arg)); + $covenant.push_token($crate::covenants::CovenantToken::output_type($arg))?; $crate::__covenant_inner!(@ { $covenant } $($tail)*) }; // @arg(expr1, expr2, ...), ... (@ { $covenant:ident } @$arg:ident($($args:expr),* $(,)?), $($tail:tt)*) => { - $covenant.push_token($crate::covenants::CovenantToken::$arg($($args),*)); + $covenant.push_token($crate::covenants::CovenantToken::$arg($($args),*))?; $crate::__covenant_inner!(@ { $covenant } $($tail)*) }; @@ -144,23 +153,24 @@ macro_rules! __covenant_inner { // token(), ... (@ { $covenant:ident } $token:ident(), $($tail:tt)*) => { - $covenant.push_token($crate::covenants::CovenantToken::$token()); + $covenant.push_token($crate::covenants::CovenantToken::$token())?; $crate::__covenant_inner!(@ { $covenant } $($tail)*) }; - // token(filter1, filter2, ...) + + // token(filter1, filter2, ...) (@ { $covenant:ident } $token:ident($($args:tt)+)) => { $crate::__covenant_inner!(@ { $covenant } $token($($args)+),) }; // token(filter1, filter2, ...), ... (@ { $covenant:ident } $token:ident($($args:tt)+), $($tail:tt)*) => { - $covenant.push_token($crate::covenants::CovenantToken::$token()); + $covenant.push_token($crate::covenants::CovenantToken::$token())?; $crate::__covenant_inner!(@ { $covenant } $($args)+ $($tail)*) }; // token(...) - (@ { $covenant:ident } $token:ident($($args:tt)+)) => { - $covenant.push_token($crate::covenants::CovenantToken::$token()); + (@ { $covenant:ident } $token:ident($($args:tt)+),) => { + $covenant.push_token($crate::covenants::CovenantToken::$token())?; $crate::__covenant_inner!(@ { $covenant } $($args)+) }; } @@ -179,7 +189,7 @@ mod test { #[test] fn simple() { - let covenant = covenant!(identity()); + let covenant = covenant!(identity()).unwrap(); assert_eq!(covenant.tokens().len(), 1); assert!(matches!( covenant.tokens()[0], @@ -198,7 +208,7 @@ mod test { let dest_pk = PublicKey::from_hex("b0c1f788f137ba0cdc0b61e89ee43b80ebf5cca4136d3229561bf11eba347849").unwrap(); let sender_pk = dest_pk.clone(); let script = script!(HashSha256 PushHash(Box::new(hash)) Equal IfThen PushPubKey(Box::new(dest_pk)) Else CheckHeightVerify(100) PushPubKey(Box::new(sender_pk)) EndIf).unwrap(); - let covenant = covenant!(field_eq(@field::script, @script(script.clone()))); + let covenant = covenant!(field_eq(@field::script, @script(script.clone()))).unwrap(); let decoded = Covenant::from_bytes(&mut covenant.to_bytes().as_bytes()).unwrap(); assert_eq!(covenant, decoded); diff --git a/base_layer/core/src/covenants/token.rs b/base_layer/core/src/covenants/token.rs index 43d979eceb..a30d45c3e6 100644 --- a/base_layer/core/src/covenants/token.rs +++ b/base_layer/core/src/covenants/token.rs @@ -27,7 +27,7 @@ use tari_script::TariScript; use crate::{ covenants::{ - arguments::CovenantArg, + arguments::{BytesArg, CovenantArg}, decoder::{CovenantDecodeError, CovenantReadExt}, fields::OutputField, filters::{ @@ -218,7 +218,7 @@ impl CovenantToken { #[allow(dead_code)] /// Helper for creating a new instance wrapping an `BytesFilter`. - pub fn bytes(bytes: Vec) -> Self { + pub fn bytes(bytes: BytesArg) -> Self { CovenantArg::Bytes(bytes).into() } } diff --git a/base_layer/core/src/transactions/transaction_components/test.rs b/base_layer/core/src/transactions/transaction_components/test.rs index 7e3d4f4cde..950f5dd3e6 100644 --- a/base_layer/core/src/transactions/transaction_components/test.rs +++ b/base_layer/core/src/transactions/transaction_components/test.rs @@ -622,7 +622,7 @@ mod validate_internal_consistency { #[tokio::test] async fn it_validates_that_the_covenant_is_honoured() { //---------------------------------- Case1 - PASS --------------------------------------------// - let covenant = covenant!(fields_preserved(@fields( @field::covenant))); + let covenant = covenant!(fields_preserved(@fields( @field::covenant))).unwrap(); let features = OutputFeatures { ..Default::default() }; let key_manager = create_memory_db_key_manager().unwrap(); test_case( @@ -655,7 +655,7 @@ mod validate_internal_consistency { slice.copy_from_slice(hash.as_ref()); let hash = FixedHash::from(slice); - let covenant = covenant!(fields_hashed_eq(@fields(@field::features), @hash(hash))); + let covenant = covenant!(fields_hashed_eq(@fields(@field::features), @hash(hash))).unwrap(); test_case( &UtxoTestParams { @@ -673,7 +673,8 @@ mod validate_internal_consistency { .unwrap(); //---------------------------------- Case3 - FAIL --------------------------------------------// - let covenant = covenant!(or(absolute_height(@uint(100),), field_eq(@field::features_maturity, @uint(42)))); + let covenant = + covenant!(or(absolute_height(@uint(100),), field_eq(@field::features_maturity, @uint(42)))).unwrap(); let err = test_case( &UtxoTestParams { diff --git a/base_layer/core/tests/chain_storage_tests/chain_storage.rs b/base_layer/core/tests/chain_storage_tests/chain_storage.rs index b3f2018dad..fd28dd4ee5 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_storage.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_storage.rs @@ -2127,7 +2127,7 @@ mod malleability { fn test_covenant() { check_output_malleability(|block: &mut Block| { let output = &mut block.body.outputs_mut()[0]; - let mod_covenant = covenant!(absolute_height(@uint(42))); + let mod_covenant = covenant!(absolute_height(@uint(42))).unwrap(); output.covenant = mod_covenant; }); } diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 86519156fb..355571efe2 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -9863,7 +9863,7 @@ mod test { let covenant = covenant_create_from_bytes(covenant_bytes, error_ptr); assert_eq!(error, 0); - let empty_covenant = covenant!(); + let empty_covenant = covenant!().unwrap(); assert_eq!(*covenant, empty_covenant); covenant_destroy(covenant); @@ -9877,7 +9877,7 @@ mod test { let mut error = 0; let error_ptr = &mut error as *mut c_int; - let expected_covenant = covenant!(identity()); + let expected_covenant = covenant!(identity()).unwrap(); let covenant_bytes = Box::into_raw(Box::new(ByteVector(borsh::to_vec(&expected_covenant).unwrap()))); let covenant = covenant_create_from_bytes(covenant_bytes, error_ptr);