Skip to content

Commit

Permalink
feat(macro): support embedding abi as literal json (#79)
Browse files Browse the repository at this point in the history
* feat: support embedding abi as literal json

* fix: reset file pointer

* chore: remove dbg! macro

* add example

* update example

* clippy
  • Loading branch information
kariy authored Jan 21, 2025
1 parent 355b88b commit d83de93
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 37 deletions.
94 changes: 57 additions & 37 deletions crates/rs-macro/src/macro_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
//! passed to the macro. We should then parse the
//! token stream to ensure the arguments are correct.
//!
//! At this moment, the macro supports one fashion:
//! The macro supports two forms:
//!
//! Loading from a file with only the ABI array.
//! abigen!(ContractName, "path/to/abi.json"
//! 1. Loading from a file with the ABI array:
//! abigen!(ContractName, "path/to/abi.json")
//!
//! 2. Direct JSON array input:
//! abigen!(ContractName, [{"type": "function", ...}])
//!
//! TODO: support the full artifact JSON to be able to
//! deploy contracts from abigen.
Expand All @@ -16,6 +19,7 @@ use quote::ToTokens;
use starknet::core::types::contract::{AbiEntry, SierraClass};
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufReader, Seek, SeekFrom};
use std::path::Path;
use std::str::FromStr;
use syn::{
Expand Down Expand Up @@ -49,42 +53,58 @@ impl Parse for ContractAbi {

// ABI path or content.

// Path rooted to the Cargo.toml location if it's a file.
let abi_or_path = input.parse::<LitStr>()?;

#[allow(clippy::collapsible_else_if)]
let abi = if abi_or_path.value().ends_with(".json") {
let json_path = if abi_or_path.value().starts_with(CARGO_MANIFEST_DIR) {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let new_dir = Path::new(manifest_dir)
.join(abi_or_path.value().trim_start_matches(CARGO_MANIFEST_DIR))
.to_string_lossy()
.to_string();

LitStr::new(&new_dir, proc_macro2::Span::call_site())
} else {
abi_or_path
};
// Parse either a JSON array literal or a path to the contract class artifact.
//
// If the input starts with a `[` token then we parse it as a JSON array.
let abi = if input.peek(syn::token::Bracket) {
let array_content = input.parse::<proc_macro2::TokenStream>()?;
let array_str = array_content.to_string();

// To prepare the declare and deploy features, we also
// accept a full Sierra artifact for the ABI.
// To support declare and deploy, the full class must be stored.
if let Ok(sierra) =
serde_json::from_reader::<_, SierraClass>(open_json_file(&json_path.value())?)
{
sierra.abi
} else {
serde_json::from_reader::<_, Vec<AbiEntry>>(open_json_file(&json_path.value())?)
.map_err(|e| {
syn::Error::new(json_path.span(), format!("JSON parse error: {}", e))
})?
}
} else {
if let Ok(sierra) = serde_json::from_str::<SierraClass>(&abi_or_path.value()) {
sierra.abi
serde_json::from_str::<Vec<AbiEntry>>(&array_str)
.map_err(|e| syn::Error::new(input.span(), format!("Invalid ABI format: {e}")))?
}
// Otherwise, parse it either as a path to the full JSON contract class artifact
else {
// Handle file path case
let abi_str_or_path = input.parse::<LitStr>()?;

if abi_str_or_path.value().ends_with(".json") {
let json_path = if abi_str_or_path.value().starts_with(CARGO_MANIFEST_DIR) {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let new_dir = Path::new(manifest_dir)
.join(
abi_str_or_path
.value()
.trim_start_matches(CARGO_MANIFEST_DIR),
)
.to_string_lossy()
.to_string();

LitStr::new(&new_dir, proc_macro2::Span::call_site())
} else {
abi_str_or_path
};

let mut file = open_json_file(&json_path.value())?;

// To prepare the declare and deploy features, we also
// accept a full Sierra artifact for the ABI.
// To support declare and deploy, the full class must be stored.
if let Ok(class) = serde_json::from_reader::<_, SierraClass>(BufReader::new(&file))
{
class.abi
} else {
// Reset the file pointer back to the beginning of the file.
let pos = SeekFrom::Start(0);
file.seek(pos).expect("failed to reset file pointer");

serde_json::from_reader::<_, Vec<AbiEntry>>(BufReader::new(&file)).map_err(
|e| syn::Error::new(json_path.span(), format!("JSON parse error: {e}")),
)?
}
} else {
serde_json::from_str::<Vec<AbiEntry>>(&abi_or_path.value()).map_err(|e| {
syn::Error::new(abi_or_path.span(), format!("JSON parse error: {}", e))
serde_json::from_str::<Vec<AbiEntry>>(&abi_str_or_path.value()).map_err(|e| {
syn::Error::new(abi_str_or_path.span(), format!("JSON parse error: {}", e))
})?
}
};
Expand Down
71 changes: 71 additions & 0 deletions examples/json_abi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use cainome::rs::abigen;
use starknet::{
macros::felt,
providers::{jsonrpc::HttpTransport, JsonRpcClient},
};
use url::Url;

abigen!(MyContractFile, "./contracts/abi/simple_types.abi.json",);

abigen!(
MyContractLitStr,
"[{\"type\":\"function\",\"name\":\"get_bool\",\
\"inputs\":[],\"outputs\":[{\"type\":\"core::bool\"}],\"state_mutability\":\
\"view\"},{\"type\":\"function\",\"name\":\"get_felt\",\"inputs\":[],\"outputs\":\
[{\"type\":\"core::felt252\"}],\"state_mutability\":\"view\"}]",
);

abigen!(MyContractEmbed, [
{
"type": "function",
"name": "get_bool",
"inputs": [],
"outputs": [
{
"type": "core::bool"
}
],
"state_mutability": "view"
},
{
"type": "function",
"name": "set_bool",
"inputs": [
{
"name": "v",
"type": "core::bool"
}
],
"outputs": [],
"state_mutability": "external"
},
{
"type": "function",
"name": "get_felt",
"inputs": [],
"outputs": [
{
"type": "core::felt252"
}
],
"state_mutability": "view"
}
]);

#[tokio::main]
async fn main() {
let url = Url::parse("http://localhost:5050").unwrap();
let provider = JsonRpcClient::new(HttpTransport::new(url));

let contract = MyContractEmbedReader::new(felt!("0x1337"), &provider);
let _ = contract.get_bool().call().await.unwrap();
let _ = contract.get_felt().call().await.unwrap();

let contract = MyContractFileReader::new(felt!("0x1337"), &provider);
let _ = contract.get_bool().call().await.unwrap();
let _ = contract.get_felt().call().await.unwrap();

let contract = MyContractLitStrReader::new(felt!("0x1337"), &provider);
let _ = contract.get_bool().call().await.unwrap();
let _ = contract.get_felt().call().await.unwrap();
}

0 comments on commit d83de93

Please sign in to comment.