Skip to content

Commit

Permalink
Merge pull request #34 from Trantorian1/feat/macros
Browse files Browse the repository at this point in the history
feat(macros): ✨ Added spec_version to `#[require]` macro
  • Loading branch information
antiyro authored Jan 12, 2024
2 parents dd994d0 + c5127c1 commit 1528720
Show file tree
Hide file tree
Showing 23 changed files with 147 additions and 72 deletions.
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

0 comments on commit 1528720

Please sign in to comment.