diff --git a/Cargo.lock b/Cargo.lock index eb17c76..8647ae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1256,13 +1256,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] -name = "macros" +name = "macro" version = "0.1.0" dependencies = [ + "macro_utils", "quote", "syn 2.0.48", ] +[[package]] +name = "macro_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "quote", + "serde", + "serde_json", + "starknet", + "starknet-core", + "starknet-providers", + "syn 2.0.48", + "tokio", + "url", +] + [[package]] name = "memchr" version = "2.7.1" @@ -2633,7 +2650,8 @@ dependencies = [ "flate2", "jsonrpsee", "log", - "macros", + "macro", + "macro_utils", "rstest", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 2547aab..d5a807f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] -members = [ "macros", - "macros", +members = [ + "macro_utils", + "macro", "unit_tests", ] default-members = ["unit_tests"] diff --git a/macros/Cargo.toml b/macro/Cargo.toml similarity index 67% rename from macros/Cargo.toml rename to macro/Cargo.toml index 56499ba..5c8bf08 100644 --- a/macros/Cargo.toml +++ b/macro/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "macros" +name = "macro" version = "0.1.0" edition = "2021" @@ -9,3 +9,4 @@ proc-macro = true [dependencies] quote = "1.0.35" syn = "2.0.48" +macro_utils = { path = "../macro_utils/" } \ No newline at end of file diff --git a/macro/src/lib.rs b/macro/src/lib.rs new file mode 100644 index 0000000..2c1f661 --- /dev/null +++ b/macro/src/lib.rs @@ -0,0 +1,80 @@ +use macro_utils::{extract_u64_from_expr, get_block_number}; +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, parse_quote, ItemFn, MetaNameValue, Path, Token, +}; + +#[proc_macro_attribute] +pub fn logging(_: TokenStream, input: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(input as ItemFn); + + input.block.stmts.insert( + 0, + syn::parse( + quote! { + env_logger::builder().is_test(true).try_init().err(); + } + .into(), + ) + .unwrap(), + ); + + input.into_token_stream().into() +} + +struct ArgsRequire { + pub block_min: u64, + pub block_max: u64, + pub err: Result<(), Path>, +} + +impl Parse for ArgsRequire { + fn parse(input: ParseStream) -> syn::Result { + let args = input.parse_terminated(MetaNameValue::parse, Token![,])?; + + let mut parsed_params = Self { + block_min: 0, + block_max: 0, + err: Ok(()), + }; + + for arg in args { + match arg.path.get_ident() { + Some(ident) => match ident.to_string().as_str() { + "block_min" => { + parsed_params.block_min = extract_u64_from_expr(arg.value).unwrap_or(0); + } + "block_max" => { + parsed_params.block_max = + extract_u64_from_expr(arg.value).unwrap_or(u64::MAX); + } + _ => { + parsed_params.err = Err(arg.path); + } + }, + None => todo!(), + } + } + + Ok(parsed_params) + } +} + +#[proc_macro_attribute] +pub fn require(args: TokenStream, item: TokenStream) -> TokenStream { + let bn = get_block_number(); + let args_parsed = parse_macro_input!(args as ArgsRequire); + + if bn >= args_parsed.block_min && bn <= args_parsed.block_max { + item + } else { + let mut func = parse_macro_input!(item as ItemFn); + func.attrs.push( + parse_quote!(#[ignore = "Deoxys node does not meet required specs to run this test"]), + ); + + quote!(#func).into() + } +} diff --git a/macro_utils/Cargo.toml b/macro_utils/Cargo.toml new file mode 100644 index 0000000..2f9a0f7 --- /dev/null +++ b/macro_utils/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "macro_utils" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.79" +serde = "1.0.195" +serde_json = "1.0.111" +starknet = { git = "https://github.com/xJonathanLEI/starknet-rs.git", rev = "64ebc36", default-features = false } +starknet-core = { git = "https://github.com/xJonathanLEI/starknet-rs.git", rev = "64ebc36", default-features = false } +starknet-providers = { git = "https://github.com/xJonathanLEI/starknet-rs.git", rev = "64ebc36", default-features = false } +url = "2.5.0" +syn = "2.0.48" +quote = "1.0.35" +tokio = { version = "1", features = ["full"] } diff --git a/macro_utils/src/lib.rs b/macro_utils/src/lib.rs new file mode 100644 index 0000000..da75061 --- /dev/null +++ b/macro_utils/src/lib.rs @@ -0,0 +1,50 @@ +use serde::Deserialize; +use starknet_providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider}; +use std::{fs::File, io::Read}; +use syn::{Expr, Lit}; +use tokio::runtime; +use url::Url; + +#[derive(PartialEq, Debug, Deserialize)] +pub struct TestConfig { + pub pathfinder: String, + pub deoxys: String, +} + +impl TestConfig { + pub fn new(path: &str) -> anyhow::Result { + let mut file = File::open(path)?; + let mut content = String::new(); + + file.read_to_string(&mut content)?; + + let config: TestConfig = serde_json::from_str(&content) + .expect("Could not deserialize test at {path} into Config"); + + Ok(config) + } +} + +pub fn get_block_number() -> u64 { + let config = + TestConfig::new("./secret.json").expect("'./secret.json' must contain correct node urls"); + let deoxys = JsonRpcClient::new(HttpTransport::new( + Url::parse(&config.deoxys).expect("Error parsing Deoxys node url"), + )); + + let rt = runtime::Runtime::new().unwrap(); + + rt.block_on(async { deoxys.block_number().await.unwrap() }) +} + +pub fn extract_u64_from_expr(expr: Expr) -> Result { + match expr { + Expr::Lit(expr_lit) => match expr_lit.lit { + Lit::Int(lit_int) => lit_int + .base10_parse::() + .map_err(|_| "Failed to parse integer".to_string()), + _ => Err("Not an integer literal".to_string()), + }, + _ => Err("Not a literal expression".to_string()), + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs deleted file mode 100644 index 6a05815..0000000 --- a/macros/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -use proc_macro::TokenStream; -use quote::{quote, ToTokens}; -use syn::{parse_macro_input, ItemFn}; - -#[proc_macro_attribute] -pub fn logging(_: TokenStream, input: TokenStream) -> TokenStream { - let mut input = parse_macro_input!(input as ItemFn); - - input.block.stmts.insert( - 0, - syn::parse( - quote! { - env_logger::builder().is_test(true).try_init().err(); - } - .into(), - ) - .unwrap(), - ); - - input.into_token_stream().into() -} diff --git a/unit_tests/Cargo.toml b/unit_tests/Cargo.toml index 41fb12f..2255c0a 100644 --- a/unit_tests/Cargo.toml +++ b/unit_tests/Cargo.toml @@ -8,18 +8,19 @@ edition = "2021" [dependencies] anyhow = "1.0.79" rstest = "0.18.2" -serde = "1.0.194" -serde_json = "1.0.110" tokio = { version = "1", features = ["full"] } -url = "2.5.0" starknet = { git = "https://github.com/xJonathanLEI/starknet-rs.git", rev = "64ebc36", default-features = false } starknet-core = { git = "https://github.com/xJonathanLEI/starknet-rs.git", rev = "64ebc36", default-features = false } starknet-providers = { git = "https://github.com/xJonathanLEI/starknet-rs.git", rev = "64ebc36", default-features = false } env_logger = "0.10.1" +macro_utils = { path = "../macro_utils/" } +url = "2.5.0" [dev-dependencies] jsonrpsee = { version = "0.21.0", features = ["client"] } tokio = { version = "1", features = ["full", "test-util"] } flate2 = "1.0.28" log = "0.4.20" -macros = { path = "../macros/" } \ No newline at end of file +macro = { path = "../macro/" } +serde = "1.0.195" +serde_json = "1.0.111" diff --git a/unit_tests/src/fixtures.rs b/unit_tests/src/fixtures.rs index a29718a..a7211e4 100644 --- a/unit_tests/src/fixtures.rs +++ b/unit_tests/src/fixtures.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; +use macro_utils::TestConfig; use rstest::fixture; use starknet_providers::{jsonrpc::HttpTransport, JsonRpcClient}; use url::Url; use crate::constants::*; use crate::map; -use crate::TestConfig; #[fixture] pub fn config() -> TestConfig { diff --git a/unit_tests/src/lib.rs b/unit_tests/src/lib.rs index b415ff8..d1d3efd 100644 --- a/unit_tests/src/lib.rs +++ b/unit_tests/src/lib.rs @@ -1,9 +1,6 @@ #![feature(assert_matches)] -use std::{fs::File, io::Read}; - use constants::*; -use serde::Deserialize; use starknet_core::{ types::{BroadcastedInvokeTransaction, BroadcastedTransaction, FieldElement}, utils::get_selector_from_name, @@ -13,25 +10,6 @@ pub mod constants; pub mod fixtures; pub mod macros; -#[derive(PartialEq, Debug, Deserialize)] -pub struct TestConfig { - pub pathfinder: String, - pub deoxys: String, -} - -impl TestConfig { - pub fn new(path: &str) -> anyhow::Result { - let mut file = File::open(path)?; - let mut content = String::new(); - - file.read_to_string(&mut content)?; - - let config: TestConfig = serde_json::from_str(&content) - .expect("Could not deserialize test at {path} into Config"); - - Ok(config) - } -} pub trait TransactionFactory { fn build(nonce: Option) -> BroadcastedTransaction; } diff --git a/unit_tests/tests/common.rs b/unit_tests/tests/common.rs index 219ff24..dc77fb2 100644 --- a/unit_tests/tests/common.rs +++ b/unit_tests/tests/common.rs @@ -1,7 +1,7 @@ /* Common imports used throughout all unit tests */ #[allow(unused_imports)] -pub use macros::*; +pub use r#macro::*; #[allow(unused_imports)] pub use rstest::*; #[allow(unused_imports)] diff --git a/unit_tests/tests/test_get_class_at.rs b/unit_tests/tests/test_get_class_at.rs index 4515fad..0b0495d 100644 --- a/unit_tests/tests/test_get_class_at.rs +++ b/unit_tests/tests/test_get_class_at.rs @@ -129,12 +129,13 @@ async fn work_contract_v0( /// purpose: gets Cairo v1 contract and extracts it's data. /// success case: should retrieve contract correctly. /// +#[require(block_min = 3000)] #[rstest] #[tokio::test] -async fn work_contract_v1(clients: HashMap>) { - let deoxys = &clients[DEOXYS]; - let pathfinder = &clients[PATHFINDER]; - +async fn work_contract_v1( + deoxys: JsonRpcClient, + pathfinder: JsonRpcClient, +) { let response_deoxys = deoxys .get_class_at( BlockId::Tag(BlockTag::Latest), diff --git a/unit_tests/tests/test_get_nonce.rs b/unit_tests/tests/test_get_nonce.rs index bd42fa2..1fe33f8 100644 --- a/unit_tests/tests/test_get_nonce.rs +++ b/unit_tests/tests/test_get_nonce.rs @@ -91,6 +91,7 @@ async fn fail_non_existing_contract(clients: HashMap>) { @@ -113,6 +114,7 @@ async fn work_erc721_contract(clients: HashMap>) { @@ -135,6 +137,7 @@ async fn work_erc20_contract(clients: HashMap>) { @@ -167,6 +170,7 @@ async fn work_account_contract(clients: HashMap>) { diff --git a/unit_tests/tests/test_get_transaction_receipt.rs b/unit_tests/tests/test_get_transaction_receipt.rs new file mode 100644 index 0000000..9909698 --- /dev/null +++ b/unit_tests/tests/test_get_transaction_receipt.rs @@ -0,0 +1,132 @@ +#![feature(assert_matches)] + +mod common; +use common::*; + +use std::{assert_matches::assert_matches, collections::HashMap}; + +use starknet_core::types::{FieldElement, StarknetError}; +use starknet_providers::{ + jsonrpc::{HttpTransport, JsonRpcClient}, + MaybeUnknownErrorCode, Provider, ProviderError, StarknetErrorWithMessage, +}; + +// invalid transaction_hash +#[rstest] +#[tokio::test] +async fn fail_invalid_transaction_hash(clients: HashMap>) { + let deoxys = &clients[DEOXYS]; + + let response_deoxys = deoxys + .get_transaction_receipt(FieldElement::ZERO) + .await + .err(); + + assert_matches!( + response_deoxys, + Some(ProviderError::StarknetError(StarknetErrorWithMessage { + message: _, + code: MaybeUnknownErrorCode::Known(StarknetError::TransactionHashNotFound) + })) + ); +} + +/// reverted transaction on block 200000 +#[rstest] +#[tokio::test] +async fn work_with_rejected_transaction(clients: HashMap>) { + let deoxys = &clients[DEOXYS]; + let pathfinder = &clients[PATHFINDER]; + + let transaction_hash = FieldElement::from_hex_be( + "0x410e4d74a2322b78d2e342ac376ea555c89b1a0fe73bb36067eb149da123dd1", + ) + .expect("Error parsing transaction hash"); + + let response_deoxys = deoxys + .get_transaction_receipt(transaction_hash) + .await + .expect("Error waiting for response from Deoxys node"); + + let response_pathfinder = pathfinder + .get_transaction_receipt(transaction_hash) + .await + .expect("Error waiting for response from Pathfinder node"); + + assert_eq!(response_deoxys, response_pathfinder); +} + +/// first transaction on block 0 +#[rstest] +#[tokio::test] +async fn work_with_first_transaction(clients: HashMap>) { + let deoxys = &clients[DEOXYS]; + let pathfinder = &clients[PATHFINDER]; + + let transaction_hash = FieldElement::from_hex_be( + "0xe0a2e45a80bb827967e096bcf58874f6c01c191e0a0530624cba66a508ae75", + ) + .expect("Error parsing transaction hash"); + + let response_deoxys = deoxys + .get_transaction_receipt(transaction_hash) + .await + .expect("Error waiting for response from Deoxys node"); + + let response_pathfinder = pathfinder + .get_transaction_receipt(transaction_hash) + .await + .expect("Error waiting for response from Pathfinder node"); + + assert_eq!(response_deoxys, response_pathfinder); +} + +/// deploy transaction +#[rstest] +#[tokio::test] +async fn work_with_deploy(clients: HashMap>) { + let deoxys = &clients[DEOXYS]; + let pathfinder = &clients[PATHFINDER]; + + let transaction_hash = FieldElement::from_hex_be( + "0x12c96ae3c050771689eb261c9bf78fac2580708c7f1f3d69a9647d8be59f1e1", + ) + .expect("Error parsing transaction hash"); + + let response_deoxys = deoxys + .get_transaction_receipt(transaction_hash) + .await + .expect("Error waiting for response from Deoxys node"); + + let response_pathfinder = pathfinder + .get_transaction_receipt(transaction_hash) + .await + .expect("Error waiting for response from Pathfinder node"); + + assert_eq!(response_deoxys, response_pathfinder); +} + +///invoke transaction +#[rstest] +#[tokio::test] +async fn work_with_invoke(clients: HashMap>) { + let deoxys = &clients[DEOXYS]; + let pathfinder = &clients[PATHFINDER]; + + let transaction_hash = FieldElement::from_hex_be( + "0xce54bbc5647e1c1ea4276c01a708523f740db0ff5474c77734f73beec2624", + ) + .expect("Error parsing transaction hash"); + + let response_deoxys = deoxys + .get_transaction_receipt(transaction_hash) + .await + .expect("Error waiting for response from Deoxys node"); + + let response_pathfinder = pathfinder + .get_transaction_receipt(transaction_hash) + .await + .expect("Error waiting for response from Pathfinder node"); + + assert_eq!(response_deoxys, response_pathfinder); +}