Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(macros): ✨ Added spec_version to #[require] macro #34

Merged
merged 2 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 1 addition & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,50 +79,4 @@ async fn work_existing_block(clients: HashMap<String, JsonRpcClient<HttpTranspor
let response_pathfinder = pathfinder.block_number().await.expect("RPC : Error while getting the block number");

assert_eq!(response_deoxys, response_pathfinder);
}
<<<<<<< Updated upstream
```

config file fields:
- `tests`: all the rpc calls to test against.
- `cmd`: the rpc command to query the node with.
- `arg`: the arguments used during the rpc call.
- *`block_range`* **(optional)**: the range of starknet blocks to run the unit test against.

Each test specified in `tests` will result in an RPC call to the specified Pathfinder and Deoxys nodes, comparing each result. Tests marked with `block_range` will be run against each block in the specified range. Please note that this significantly lengthens test time and should only be used for non-trivial calls whose functioning might have been different in earlier versions of the blockchain.

### In `lib.rs`

You must provide a structure specifying the format of the rpc call return value as well as a test with the path to the required json test config file.

*example test:*
```rust
#[cfg(test)]
mod tests {
use anyhow::*;
use serde::*;
use rpc_test_attribute::*;

#[derive(Deserialize, Serialize, Debug, PartialEq, Default)]
struct BlockData {
block_hash: String,
block_number: u32,
}

#[rpc_test(BlockData, "./unit/test.json")]
fn block_data_test() {}
}
```

## Structure Format

> ⚠️ Structure members must have the **exact same name** as the json fields expected as an RPC call result.

- For fields which are themselves a JSON object, use another struct to represent this sub-object.
- For fields that might be optional, use `Option`.

Try and test as many edge cases as possible. Ths mostly includes optional parameters. You can find a list
of Starknet RPC call and their arguments / return data [here](https://playground.open-rpc.org/?uiSchema%5BappBar%5D%5Bui:splitView%5D=false&schemaUrl=https://raw.githubusercontent.com/starkware-libs/starknet-specs/master/api/starknet_api_openrpc.json&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:darkMode%5D=true&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false)
=======
```
>>>>>>> Stashed changes
}
41 changes: 32 additions & 9 deletions macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use macro_utils::{extract_u64_from_expr, get_block_number};
use macro_utils::{extract_expr_to_str, extract_expr_to_u64, get_rpc_data, RpcData};
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{
Expand All @@ -24,31 +24,39 @@ pub fn logging(_: TokenStream, input: TokenStream) -> TokenStream {
input.into_token_stream().into()
}

struct ArgsRequire {
struct MacroDataRequire {
pub block_min: u64,
pub block_max: u64,
pub spec_version: Option<String>,
pub err: Result<(), Path>,
}

impl Parse for ArgsRequire {
impl Parse for MacroDataRequire {
fn parse(input: ParseStream) -> syn::Result<Self> {
let args = input.parse_terminated(MetaNameValue::parse, Token![,])?;

let mut parsed_params = Self {
block_min: 0,
block_max: 0,
block_max: u64::MAX,
spec_version: None,
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);
parsed_params.block_min = extract_expr_to_u64(arg.value).unwrap_or(0);
}
"block_max" => {
parsed_params.block_max =
extract_u64_from_expr(arg.value).unwrap_or(u64::MAX);
extract_expr_to_u64(arg.value).unwrap_or(u64::MAX);
}
"spec_version" => {
parsed_params.spec_version = match extract_expr_to_str(arg.value) {
Ok(s) => Some(s),
Err(_) => None,
}
}
_ => {
parsed_params.err = Err(arg.path);
Expand All @@ -62,12 +70,27 @@ impl Parse for ArgsRequire {
}
}

impl MacroDataRequire {
fn should_ignore(self, data: RpcData) -> bool {
let Self {
block_min,
block_max,
spec_version,
err: _,
} = self;

(data.block_number >= block_min)
&& (data.block_number <= block_max)
&& (data.spec_version == spec_version.unwrap_or(String::from("")))
}
}

#[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);
let block_data = get_rpc_data();
let macro_data = parse_macro_input!(args as MacroDataRequire);

if bn >= args_parsed.block_min && bn <= args_parsed.block_max {
if macro_data.should_ignore(block_data) {
item
} else {
let mut func = parse_macro_input!(item as ItemFn);
Expand Down
38 changes: 30 additions & 8 deletions macro_utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::anyhow;
use serde::Deserialize;
use starknet_providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider};
use std::{fs::File, io::Read};
Expand Down Expand Up @@ -25,7 +26,12 @@ impl TestConfig {
}
}

pub fn get_block_number() -> u64 {
pub struct RpcData {
pub block_number: u64,
pub spec_version: String,
}

pub fn get_rpc_data() -> RpcData {
let config =
TestConfig::new("./secret.json").expect("'./secret.json' must contain correct node urls");
let deoxys = JsonRpcClient::new(HttpTransport::new(
Expand All @@ -34,17 +40,33 @@ pub fn get_block_number() -> u64 {

let rt = runtime::Runtime::new().unwrap();

rt.block_on(async { deoxys.block_number().await.unwrap() })
rt.block_on(async {
RpcData {
block_number: deoxys.block_number().await.unwrap(),
spec_version: deoxys.spec_version().await.unwrap(),
}
})
}

pub fn extract_expr_to_str(expr: Expr) -> anyhow::Result<String> {
match expr {
Expr::Lit(expr_lit) => match expr_lit.lit {
Lit::Str(lit_str) => anyhow::Ok(lit_str.value()),
_ => Err(anyhow!("Not a string literal")),
},
_ => Err(anyhow!("Not a literal expression")),
}
}

pub fn extract_u64_from_expr(expr: Expr) -> Result<u64, String> {
pub fn extract_expr_to_u64(expr: Expr) -> anyhow::Result<u64> {
match expr {
Expr::Lit(expr_lit) => match expr_lit.lit {
Lit::Int(lit_int) => lit_int
.base10_parse::<u64>()
.map_err(|_| "Failed to parse integer".to_string()),
_ => Err("Not an integer literal".to_string()),
Lit::Int(lit_int) => match lit_int.base10_parse::<u64>() {
Ok(n) => anyhow::Ok(n),
Err(_) => Err(anyhow!("Failed to convert literal")),
},
_ => Err(anyhow!("Not an integer literal")),
},
_ => Err("Not a literal expression".to_string()),
_ => Err(anyhow!("Not a literal expression")),
}
}
3 changes: 2 additions & 1 deletion unit_tests/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,5 @@ pub const ARGENT_CONTRACT_ADDRESS: &str = "";
pub const ERR_DEOXYS: &str = "Error waiting for response from Deoxys client";
pub const ERR_PATHFINDER: &str = "Error waiting for response from Pathfinder client";

pub const RPC_SPEC: &str = "0.5.1";
pub const SPEC_0_5_1: &str = "0.5.1";
pub const SPEC_0_6_0: &str = "0.6.0";
2 changes: 2 additions & 0 deletions unit_tests/tests/test_block_hash_and_number.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod common;
use common::*;
use r#macro::require;

use std::collections::HashMap;

Expand All @@ -14,6 +15,7 @@ use starknet_providers::{
/// purpose: get block hash and number on latest block.
/// success case: retrieves correct block hash and number.
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_latest_block(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down
1 change: 1 addition & 0 deletions unit_tests/tests/test_block_number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use starknet_providers::{
/// purpose: call blockNumber on latest block.
/// success case: must return valid non-zero block number.
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_existing_block(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down
9 changes: 9 additions & 0 deletions unit_tests/tests/test_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use starknet_providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider, Provid
/// purpose: function request `name` to StarkGate ETH bridge contract
/// fail case: invalid block
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn fail_non_existing_block(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down Expand Up @@ -46,6 +47,7 @@ async fn fail_non_existing_block(clients: HashMap<String, JsonRpcClient<HttpTran
/// purpose: function request `name` to StarkGate ETH bridge contract
/// fail case: invalid contract address
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn fail_non_existing_contract(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down Expand Up @@ -77,6 +79,7 @@ async fn fail_non_existing_contract(clients: HashMap<String, JsonRpcClient<HttpT
/// purpose: function request `name` to StarkGate ETH bridge contract
/// fail case: invalid field element
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn fail_invalid_contract_entry_point_selector(
Expand Down Expand Up @@ -110,6 +113,7 @@ async fn fail_invalid_contract_entry_point_selector(
/// purpose: function request `balanceOf` to StarkGate ETH bridge contract
/// fail case: missing call data. This is different from solely *invalid* call data, as we will see shortly
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn fail_missing_contract_call_data(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down Expand Up @@ -141,6 +145,7 @@ async fn fail_missing_contract_call_data(clients: HashMap<String, JsonRpcClient<
/// purpose: function request `balanceOf` to StarkGate ETH bridge contract
/// fail case: invalid call data. This does not cause an error upon calling the contract but returns felt 0x0
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn fail_invalid_contract_call_data(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down Expand Up @@ -170,6 +175,7 @@ async fn fail_invalid_contract_call_data(clients: HashMap<String, JsonRpcClient<
/// purpose: function request `name` to StarkGate ETH bridge contract
/// fail case: too many arguments in call data
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn fail_too_many_call_data(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down Expand Up @@ -199,6 +205,7 @@ async fn fail_too_many_call_data(clients: HashMap<String, JsonRpcClient<HttpTran
/// purpose: function request `name` to StarkGate ETH bridge contract
/// success case: should return 'Ether'
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_correct_call(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down Expand Up @@ -241,6 +248,7 @@ async fn work_correct_call(clients: HashMap<String, JsonRpcClient<HttpTransport>
/// purpose: function request `balanceOf` to StarkGate ETH bridge contract
/// success case: must return non-zero balance
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_correct_call_with_args(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down Expand Up @@ -283,6 +291,7 @@ async fn work_correct_call_with_args(clients: HashMap<String, JsonRpcClient<Http
/// purpose: function request `sort_tokens` to JediSwap exchange, with multiple arguments.
/// success case: must return array of 2 non-zero values.
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_with_multiple_args(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down
1 change: 1 addition & 0 deletions unit_tests/tests/test_chain_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::collections::HashMap;
/// purpose: get currently configured Starknet chain id
/// success case: retrieve correct chain id
///
#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn chain_id(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand Down
4 changes: 4 additions & 0 deletions unit_tests/tests/test_estimate_message_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub fn get_message_from_l1(
}
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
#[ignore = "Need to fix unwrap on error due to empty constants"]
Expand All @@ -64,6 +65,7 @@ async fn fail_non_existing_block(clients: HashMap<String, JsonRpcClient<HttpTran
);
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
#[ignore = "Need to fix unwrap on error due to empty constants"]
Expand Down Expand Up @@ -91,6 +93,7 @@ async fn fail_contract_not_found(clients: HashMap<String, JsonRpcClient<HttpTran
)
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
#[ignore = "Need to fix unwrap on error due to empty constants"]
Expand Down Expand Up @@ -119,6 +122,7 @@ async fn fail_contract_error(clients: HashMap<String, JsonRpcClient<HttpTranspor
)
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
#[ignore = "Need to fix unwrap on error due to empty constants"]
Expand Down
6 changes: 6 additions & 0 deletions unit_tests/tests/test_get_block_transaction_count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use starknet_providers::{
};
use unit_tests::constants::DEOXYS;

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn fail_non_existing_block(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand All @@ -29,6 +30,7 @@ async fn fail_non_existing_block(clients: HashMap<String, JsonRpcClient<HttpTran
);
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_with_latest_block(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand All @@ -50,6 +52,7 @@ async fn work_with_latest_block(clients: HashMap<String, JsonRpcClient<HttpTrans
assert_eq!(response_deoxys, response_pathfinder);
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_with_block_one_num(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand All @@ -71,6 +74,7 @@ async fn work_with_block_one_num(clients: HashMap<String, JsonRpcClient<HttpTran
assert_eq!(response_deoxys, response_pathfinder);
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_with_block_one_hash(clients: HashMap<String, JsonRpcClient<HttpTransport>>) {
Expand All @@ -97,6 +101,7 @@ async fn work_with_block_one_hash(clients: HashMap<String, JsonRpcClient<HttpTra
assert_eq!(response_deoxys, response_pathfinder);
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_with_block_one_hundred_thousand_num(
Expand All @@ -120,6 +125,7 @@ async fn work_with_block_one_hundred_thousand_num(
assert_eq!(response_deoxys, response_pathfinder);
}

#[require(spec_version = "0.5.1")]
#[rstest]
#[tokio::test]
async fn work_with_block_one_hundred_thousand_hash(
Expand Down
Loading
Loading