diff --git a/Scarb.lock b/Scarb.lock index 8beea944..03a2f9e7 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -12,9 +12,19 @@ dependencies = [ name = "consensus" version = "0.1.0" dependencies = [ + "logging", + "raito_macros", "utils", ] +[[package]] +name = "logging" +version = "0.1.0" + +[[package]] +name = "raito_macros" +version = "0.1.0" + [[package]] name = "utils" version = "0.1.0" diff --git a/Scarb.toml b/Scarb.toml index 95012dcc..93c10dc0 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -11,3 +11,4 @@ license-file = "LICENSE" [workspace.dependencies] cairo_test = "2.8.0" + diff --git a/packages/consensus/Scarb.toml b/packages/consensus/Scarb.toml index 7ca44ee0..f0793de2 100644 --- a/packages/consensus/Scarb.toml +++ b/packages/consensus/Scarb.toml @@ -5,10 +5,11 @@ edition = "2024_07" [dependencies] utils = { path = "../utils" } - +raito_macros = { path = "../macros" } +logging = { path = "../logging" } [dev-dependencies] cairo_test.workspace = true [scripts] # TODO: cairo lint -lint = "scarb fmt" +lint = "scarb fmt" \ No newline at end of file diff --git a/packages/consensus/src/codec.cairo b/packages/consensus/src/codec.cairo index ac5820d4..4c679a33 100644 --- a/packages/consensus/src/codec.cairo +++ b/packages/consensus/src/codec.cairo @@ -2,7 +2,6 @@ use super::types::transaction::{Transaction, TxIn, TxOut, OutPoint}; use utils::hash::Digest; - pub trait Encode { /// Encode using Bitcoin codec and append to the buffer. fn encode_to(self: @T, ref dest: ByteArray); @@ -12,6 +11,7 @@ pub trait Encode { self: @T ) -> ByteArray { let mut dest: ByteArray = Default::default(); + if logging::log::LOG_LEVEL_DEBUG { println!("{}: {}", format!("DEBUG"), format!("tx_encoded: {}", dest)) } Self::encode_to(self, ref dest); dest } @@ -767,7 +767,6 @@ mod tests { "020000000001013ce63f3c9d1375b3d7fc59516dbe57120fe3c912a31ebc29241897b16215cc390000000000fdffffff020f900100000000001976a914998db5e1126bc3a5e04109fbf253a7900462410e88acd9bd150000000000160014579bf4f06510c8683f2451262b6685b00012e46f024730440220537f470c1a18dc1a9d233c0b6af1d2ce18a07f3b244e4d9d54e0e60c34c55e67022058169cd11ac42374cda217d6e28143abd0e79549f7b84acc6542817466dc9b3001210301c1768b48843933bd7f0e8782716e8439fc44723d3745feefde2d57b761f5033f600a00" ); let total_weight = 3 * tx_encoded.len() + wtx_encoded.len(); - assert_eq!(tx_encoded, tx_encoded_expect); assert_eq!(wtx_encoded, wtx_encoded_expect); assert_eq!(total_weight, 3 * 116 + 225); //573 diff --git a/packages/consensus/src/validation/timestamp.cairo b/packages/consensus/src/validation/timestamp.cairo index bfd1fcfb..1446207d 100644 --- a/packages/consensus/src/validation/timestamp.cairo +++ b/packages/consensus/src/validation/timestamp.cairo @@ -1,7 +1,6 @@ //! Block time validation helpers. //! //! Read more: https://learnmeabitcoin.com/technical/block/time/ - /// Check that the block time is greater than the median of the 11 most recent timestamps. pub fn validate_timestamp(prev_timestamps: Span, block_time: u32) -> Result<(), ByteArray> { // Sort the last 11 timestamps @@ -55,7 +54,6 @@ pub fn next_prev_timestamps(prev_timestamps: Span, block_time: u32) -> Span #[cfg(test)] mod tests { use super::{validate_timestamp, next_prev_timestamps}; - #[test] fn test_validate_timestamp() { let prev_timestamps = array![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].span(); diff --git a/packages/logging/.gitignore b/packages/logging/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/packages/logging/.gitignore @@ -0,0 +1 @@ +target diff --git a/packages/logging/Scarb.toml b/packages/logging/Scarb.toml new file mode 100644 index 00000000..5f646ca9 --- /dev/null +++ b/packages/logging/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "logging" +version = "0.1.0" +edition = "2024_07" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +[dev-dependencies] +cairo_test = "2.8.2" + +[features] +default = ["log_level_none"] +log_level_trace = [] +log_level_debug = [] +log_level_none = [] \ No newline at end of file diff --git a/packages/logging/src/lib.cairo b/packages/logging/src/lib.cairo new file mode 100644 index 00000000..7e8abe20 --- /dev/null +++ b/packages/logging/src/lib.cairo @@ -0,0 +1 @@ +pub mod log; \ No newline at end of file diff --git a/packages/logging/src/log.cairo b/packages/logging/src/log.cairo new file mode 100644 index 00000000..c9e8367f --- /dev/null +++ b/packages/logging/src/log.cairo @@ -0,0 +1,18 @@ +#[cfg(feature: 'log_level_trace')] +pub const LOG_LEVEL_TRACE: bool = true; + +#[cfg(feature: 'log_level_trace')] +pub const LOG_LEVEL_DEBUG: bool = true; + +#[cfg(feature: 'log_level_debug')] +pub const LOG_LEVEL_TRACE: bool = false; + +#[cfg(feature: 'log_level_debug')] +pub const LOG_LEVEL_DEBUG: bool = true; + +#[cfg(feature: 'log_level_none')] +pub const LOG_LEVEL_TRACE: bool = false; + +#[cfg(feature: 'log_level_none')] +pub const LOG_LEVEL_DEBUG: bool = false; + diff --git a/packages/macros/.gitignore b/packages/macros/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/packages/macros/.gitignore @@ -0,0 +1 @@ +target diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml new file mode 100644 index 00000000..39af1d49 --- /dev/null +++ b/packages/macros/Cargo.toml @@ -0,0 +1,14 @@ +# Cargo.toml +[package] +name = "raito_macros" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +bigdecimal = "0.4.5" +cairo-lang-macro = "0.1" +cairo-lang-parser = "2.8.0" +cairo-lang-syntax = "2.8.0" diff --git a/packages/macros/Scarb.toml b/packages/macros/Scarb.toml new file mode 100644 index 00000000..66fff079 --- /dev/null +++ b/packages/macros/Scarb.toml @@ -0,0 +1,7 @@ +[package] +name = "raito_macros" +version = "0.1.0" +edition = "2024_07" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +[cairo-plugin] diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs new file mode 100644 index 00000000..31348d2f --- /dev/null +++ b/packages/macros/src/lib.rs @@ -0,0 +1 @@ +pub mod logging; diff --git a/packages/macros/src/logging.rs b/packages/macros/src/logging.rs new file mode 100644 index 00000000..21a26729 --- /dev/null +++ b/packages/macros/src/logging.rs @@ -0,0 +1,152 @@ +use cairo_lang_macro::{inline_macro, Diagnostic, ProcMacroResult, TokenStream}; +use cairo_lang_parser::utils::SimpleParserDatabase; +use cairo_lang_syntax::node::kind::SyntaxKind; + +#[inline_macro] +pub fn log(token_stream: TokenStream) -> ProcMacroResult { + let db = SimpleParserDatabase::default(); + let (parsed, _diag) = db.parse_virtual_with_diagnostics(token_stream); + + let mut args = parsed + .descendants(&db) + .filter(|node| matches!(node.kind(&db), SyntaxKind::Arg)); + + // Extract log level + let level = match args.next() { + Some(node) => node.get_text(&db), + _ => return create_error_result("Invalid log level"), + }; + + // Validate log level + if !["\"TRACE\"", "\"DEBUG\""].contains(&level.as_str()) { + return create_error_result("Invalid log level. Use TRACE or DEBUG"); + } + + // Extract format string + let format_string = match args.next() { + Some(node) => node.get_text(&db), + _ => return create_error_result("Invalid format string"), + }; + + // Extract remaining arguments + let log_args: Vec = args + .map(|node| { + if matches!(node.kind(&db), SyntaxKind::Arg) { + Ok(node.get_text(&db)) + } else { + Err(()) + } + }) + .collect::, ()>>() + .unwrap_or_else(|_| { + create_error_result("Invalid log argument"); + Vec::new() + }); + + // Generate the log statement + let log_statement = generate_log_statement(&level, &format_string, &log_args); + + println!("Log statement: {}", log_statement); + ProcMacroResult::new(TokenStream::new(log_statement)) +} + +fn create_error_result(message: &str) -> ProcMacroResult { + ProcMacroResult::new(TokenStream::empty()).with_diagnostics(Diagnostic::error(message).into()) +} + +fn generate_log_statement(level: &str, format_string: &str, log_args: &[String]) -> String { + let level_str = level.trim_matches('"'); + let condition = match level_str { + "TRACE" => "LOG_LEVEL_TRACE", + "DEBUG" => "LOG_LEVEL_DEBUG", + _ => unreachable!(), + }; + let level_str = format!("{}", level); + let print_statement = if log_args.is_empty() { + format!( + "println!(\"{{}}: {{}}\", format!({}), format!({}))", + level_str, format_string + ) + } else { + format!( + "println!(\"{{}}: {{}}\", format!({}), format!({}, {}))", + level_str, + format_string, + log_args.join(", ") + ) + }; + + format!("if logging::{} {{ {} }}", condition, print_statement) +} +mod tests { + use super::*; + use cairo_lang_macro::TokenStream; + + // Helper function to expand macros + #[allow(unused)] + fn expand_macro(macro_fn: fn(TokenStream) -> ProcMacroResult, input: &str) -> String { + let token_stream = TokenStream::new(input.to_string()); + let result = macro_fn(token_stream); + result.token_stream.to_string() + } + + #[test] + fn test_log_macro_debug() { + let expanded = expand_macro( + log_02lvnd9pht92k, + r#"log!("DEBUG", "test message: {}", '123')"#, + ); + assert_eq!( + expanded, + r#"if logging::LOG_LEVEL_DEBUG { println!("{}: {}", format!("DEBUG"), format!("test message: {}", '123')) }"# + ); + } + + #[test] + fn test_log_macro_trace() { + let expanded = expand_macro(log_02lvnd9pht92k, r#"log!("TRACE", "entering function")"#); + assert_eq!( + expanded, + r#"if logging::LOG_LEVEL_TRACE { println!("{}: {}", format!("TRACE"), format!("entering function")) }"# + ); + } + + #[test] + fn test_log_macro_invalid_level() { + let expanded = expand_macro(log_02lvnd9pht92k, r#"log!("INFO", "test message")"#); + assert_eq!(expanded, ""); // Empty TokenStream for error case + } + + #[test] + fn test_log_macro_empty_format() { + let expanded = expand_macro(log_02lvnd9pht92k, r#"log!("DEBUG", '')"#); + assert_eq!( + expanded, + r#"if logging::LOG_LEVEL_DEBUG { println!("{}: {}", format!("DEBUG"), format!('')) }"# + ); + } + + #[test] + fn test_log_macro_multiple_arguments() { + let expanded = expand_macro( + log_02lvnd9pht92k, + r#"log!("DEBUG", "Values: {}, {}, {}", '1', '2', '3')"#, + ); + assert_eq!( + expanded, + r#"if logging::LOG_LEVEL_DEBUG { println!("{}: {}", format!("DEBUG"), format!("Values: {}, {}, {}", '1', '2', '3')) }"# + ); + } + + #[test] + fn test_log_macro_exact_string() { + let expanded = expand_macro( + log_02lvnd9pht92k, + r#"log!("DEBUG", "Exact {} string", 'test')"#, + ); + assert_eq!( + expanded, + r#"if logging::LOG_LEVEL_DEBUG { println!("{}: {}", format!("DEBUG"), format!("Exact {} string", 'test')) }"# + ); + } +}