Skip to content

Commit

Permalink
feat: add execution v1 or v3 as a parameter (#42)
Browse files Browse the repository at this point in the history
* feat: add execution v1 or v3 as a parameter

* fix: fix typo
  • Loading branch information
glihm authored Jul 5, 2024
1 parent e67d925 commit 6c82c5b
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 11 deletions.
12 changes: 10 additions & 2 deletions crates/rs-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ fn abigen_internal(input: TokenStream) -> TokenStream {
let abi_tokens = AbiParser::collect_tokens(&abi_entries, &contract_abi.type_aliases)
.expect("failed tokens parsing");

let expanded = cainome_rs::abi_to_tokenstream(&contract_name.to_string(), &abi_tokens);
let expanded = cainome_rs::abi_to_tokenstream(
&contract_name.to_string(),
&abi_tokens,
contract_abi.execution_version,
);

if let Some(out_path) = contract_abi.output_path {
let content: String = expanded.to_string();
Expand All @@ -53,7 +57,11 @@ fn abigen_internal_legacy(input: TokenStream) -> TokenStream {
let abi_tokens = AbiParserLegacy::collect_tokens(&abi_entries, &contract_abi.type_aliases)
.expect("failed tokens parsing");

let expanded = cainome_rs::abi_to_tokenstream(&contract_name.to_string(), &abi_tokens);
let expanded = cainome_rs::abi_to_tokenstream(
&contract_name.to_string(),
&abi_tokens,
cainome_rs::ExecutionVersion::V1,
);

if let Some(out_path) = contract_abi.output_path {
let content: String = expanded.to_string();
Expand Down
13 changes: 13 additions & 0 deletions crates/rs-macro/src/macro_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use starknet::core::types::contract::{AbiEntry, SierraClass};
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::path::Path;
use std::str::FromStr;
use syn::{
braced,
ext::IdentExt,
Expand All @@ -25,6 +26,7 @@ use syn::{
};

use crate::spanned::Spanned;
use cainome_rs::ExecutionVersion;

const CARGO_MANIFEST_DIR: &str = "$CARGO_MANIFEST_DIR/";

Expand All @@ -34,6 +36,7 @@ pub(crate) struct ContractAbi {
pub abi: Vec<AbiEntry>,
pub output_path: Option<String>,
pub type_aliases: HashMap<String, String>,
pub execution_version: ExecutionVersion,
}

impl Parse for ContractAbi {
Expand Down Expand Up @@ -84,6 +87,7 @@ impl Parse for ContractAbi {
};

let mut output_path: Option<String> = None;
let mut execution_version = ExecutionVersion::V1;
let mut type_aliases = HashMap::new();

loop {
Expand Down Expand Up @@ -123,6 +127,14 @@ impl Parse for ContractAbi {
parenthesized!(content in input);
output_path = Some(content.parse::<LitStr>()?.value());
}
"execution_version" => {
let content;
parenthesized!(content in input);
let ev = content.parse::<LitStr>()?.value();
execution_version = ExecutionVersion::from_str(&ev).map_err(|e| {
syn::Error::new(content.span(), format!("Invalid execution version: {}", e))
})?;
}
_ => panic!("unexpected named parameter `{}`", name),
}
}
Expand All @@ -132,6 +144,7 @@ impl Parse for ContractAbi {
abi,
output_path,
type_aliases,
execution_version,
})
}
}
Expand Down
42 changes: 42 additions & 0 deletions crates/rs/src/execution_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/// Execution version of Starknet transactions.
/// The version of transaction to be executed.
#[derive(Debug, Clone, Copy, Default)]
pub enum ExecutionVersion {
/// Execute the transaction using the `execute_v1` method, where fees are only payable in WEI.
#[default]
V1,
/// Execute the transaction using the `execute_v3` method, where fees are payable in WEI or FRI.
V3,
}

#[derive(Debug, PartialEq, Eq)]
pub struct ParseExecutionVersionError {
invalid_value: String,
}

impl std::fmt::Display for ParseExecutionVersionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Invalid execution version '{}'. Supported values are 'v1', 'V1', 'v3', or 'V3'.",
self.invalid_value
)
}
}

impl std::error::Error for ParseExecutionVersionError {}

impl std::str::FromStr for ExecutionVersion {
type Err = ParseExecutionVersionError;

fn from_str(input: &str) -> Result<ExecutionVersion, Self::Err> {
match input {
"v1" | "V1" => Ok(ExecutionVersion::V1),
"v3" | "V3" => Ok(ExecutionVersion::V3),
_ => Err(ParseExecutionVersionError {
invalid_value: input.to_string(),
}),
}
}
}
30 changes: 27 additions & 3 deletions crates/rs/src/expand/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ use quote::quote;

use crate::expand::types::CairoToRust;
use crate::expand::utils;
use crate::ExecutionVersion;

impl ExecutionVersion {
pub fn get_type_str(&self) -> String {
match self {
ExecutionVersion::V1 => "starknet::accounts::ExecutionV1<A>".to_string(),
ExecutionVersion::V3 => "starknet::accounts::ExecutionV3<A>".to_string(),
}
}

pub fn get_call_str(&self) -> TokenStream2 {
match self {
ExecutionVersion::V1 => quote!(self.account.execute_v1(vec![__call])),
ExecutionVersion::V3 => quote!(self.account.execute_v3(vec![__call])),
}
}
}

fn get_func_inputs(inputs: &[(String, Token)]) -> Vec<TokenStream2> {
let mut out: Vec<TokenStream2> = vec![];
Expand All @@ -35,7 +52,11 @@ fn get_func_inputs(inputs: &[(String, Token)]) -> Vec<TokenStream2> {
pub struct CairoFunction;

impl CairoFunction {
pub fn expand(func: &Function, is_for_reader: bool) -> TokenStream2 {
pub fn expand(
func: &Function,
is_for_reader: bool,
execution_version: ExecutionVersion,
) -> TokenStream2 {
let func_name = &func.name;
let func_name_ident = utils::str_to_ident(func_name);

Expand Down Expand Up @@ -110,6 +131,9 @@ impl CairoFunction {
//
// TODO: if it's possible to do it with lifetime,
// this can be tried in an issue.
let exec_type = utils::str_to_type(&execution_version.get_type_str());
let exec_call = execution_version.get_call_str();

quote! {
#[allow(clippy::ptr_arg)]
#[allow(clippy::too_many_arguments)]
Expand All @@ -133,7 +157,7 @@ impl CairoFunction {
pub fn #func_name_ident(
&self,
#(#inputs),*
) -> starknet::accounts::ExecutionV1<A> {
) -> #exec_type {
use #ccs::CairoSerde;

let mut __calldata = vec![];
Expand All @@ -145,7 +169,7 @@ impl CairoFunction {
calldata: __calldata,
};

self.account.execute_v1(vec![__call])
#exec_call
}
}
}
Expand Down
32 changes: 27 additions & 5 deletions crates/rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use std::fmt;
use std::fs;
use std::io;

mod execution_version;
mod expand;
pub use execution_version::{ExecutionVersion, ParseExecutionVersionError};

use crate::expand::utils;
use crate::expand::{CairoContract, CairoEnum, CairoEnumEvent, CairoFunction, CairoStruct};
Expand Down Expand Up @@ -63,6 +65,8 @@ pub struct Abigen {
/// Types aliases to avoid name conflicts, as for now the types are limited to the
/// latest segment of the fully qualified path.
pub types_aliases: HashMap<String, String>,
/// The version of transaction to be executed.
pub execution_version: ExecutionVersion,
}

impl Abigen {
Expand All @@ -78,6 +82,7 @@ impl Abigen {
contract_name: contract_name.to_string(),
abi_source: Utf8PathBuf::from(abi_source),
types_aliases: HashMap::new(),
execution_version: ExecutionVersion::V1,
}
}

Expand All @@ -91,13 +96,24 @@ impl Abigen {
self
}

/// Sets the execution version to be used.
///
/// # Arguments
///
/// * `execution_version` - The version of transaction to be executed.
pub fn with_execution_version(mut self, execution_version: ExecutionVersion) -> Self {
self.execution_version = execution_version;
self
}

/// Generates the contract bindings.
pub fn generate(&self) -> Result<ContractBindings> {
let file_content = std::fs::read_to_string(&self.abi_source)?;

match AbiParser::tokens_from_abi_string(&file_content, &self.types_aliases) {
Ok(tokens) => {
let expanded = abi_to_tokenstream(&self.contract_name, &tokens);
let expanded =
abi_to_tokenstream(&self.contract_name, &tokens, self.execution_version);

Ok(ContractBindings {
name: self.contract_name.clone(),
Expand All @@ -120,7 +136,11 @@ impl Abigen {
///
/// * `contract_name` - Name of the contract.
/// * `abi_tokens` - Tokenized ABI.
pub fn abi_to_tokenstream(contract_name: &str, abi_tokens: &TokenizedAbi) -> TokenStream2 {
pub fn abi_to_tokenstream(
contract_name: &str,
abi_tokens: &TokenizedAbi,
execution_version: ExecutionVersion,
) -> TokenStream2 {
let contract_name = utils::str_to_ident(contract_name);

let mut tokens: Vec<TokenStream2> = vec![];
Expand Down Expand Up @@ -160,10 +180,12 @@ pub fn abi_to_tokenstream(contract_name: &str, abi_tokens: &TokenizedAbi) -> Tok
let f = f.to_function().expect("function expected");
match f.state_mutability {
StateMutability::View => {
reader_views.push(CairoFunction::expand(f, true));
views.push(CairoFunction::expand(f, false));
reader_views.push(CairoFunction::expand(f, true, execution_version));
views.push(CairoFunction::expand(f, false, execution_version));
}
StateMutability::External => {
externals.push(CairoFunction::expand(f, false, execution_version))
}
StateMutability::External => externals.push(CairoFunction::expand(f, false)),
}
}

Expand Down
85 changes: 85 additions & 0 deletions examples/exec_v3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use cainome::rs::abigen;
use starknet::{
accounts::{ExecutionEncoding, SingleOwnerAccount},
core::types::Felt,
providers::{jsonrpc::HttpTransport, AnyProvider, JsonRpcClient},
signers::{LocalWallet, SigningKey},
};
use std::sync::Arc;
use url::Url;

// To run this example, please first run `make setup_simple_get_set` in the contracts directory with a Katana running. This will declare and deploy the testing contract.

const CONTRACT_ADDRESS: &str = "0x007997dd654f2c079597a6c461489ee89981d0df733b8bcd3525153b0e700f98";
const KATANA_ACCOUNT_0: &str = "0x6162896d1d7ab204c7ccac6dd5f8e9e7c25ecd5ae4fcb4ad32e57786bb46e03";
const KATANA_PRIVKEY_0: &str = "0x1800000000300000180000000000030000000000003006001800006600";
const KATANA_CHAIN_ID: &str = "0x4b4154414e41";

// You can load of the sierra class entirely from the artifact.
// Or you can use the extracted abi entries with jq in contracts/abi/.
abigen!(
MyContract,
"./contracts/target/dev/contracts_simple_get_set.contract_class.json",
execution_version("V3"),
);
//abigen!(MyContract, "./contracts/abi/simple_get_set.abi.json");

#[tokio::main]
async fn main() {
let rpc_url = Url::parse("http://0.0.0.0:5050").expect("Expecting Starknet RPC URL");
let provider =
AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(rpc_url.clone())));

let contract_address = Felt::from_hex(CONTRACT_ADDRESS).unwrap();

// If you only plan to call views functions, you can use the `Reader`, which
// only requires a provider along with your contract address.
let contract = MyContractReader::new(contract_address, &provider);

// To call a view, there is no need to initialize an account. You can directly
// use the name of the method in the ABI and then use the `call()` method.
let a = contract
.get_a()
.call()
.await
.expect("Call to `get_a` failed");
println!("a initial value: {:?}", a);

// If you want to do some invoke for external functions, you must use an account.
let signer = LocalWallet::from(SigningKey::from_secret_scalar(
Felt::from_hex(KATANA_PRIVKEY_0).unwrap(),
));
let address = Felt::from_hex(KATANA_ACCOUNT_0).unwrap();

let account = Arc::new(SingleOwnerAccount::new(
provider,
signer,
address,
Felt::from_hex(KATANA_CHAIN_ID).unwrap(),
ExecutionEncoding::New,
));

// A `Contract` exposes all the methods of the ABI, which includes the views (as the `ContractReader`) and
// the externals (sending transaction).
let contract = MyContract::new(contract_address, account);

// The transaction is actually sent when `send()` is called.
// You can before that configure the fees, or even only run an estimation of the
// fees without actually sending the transaction.
let _tx_res = contract
.set_a(&(a + Felt::ONE))
.gas_estimate_multiplier(1.2)
.send()
.await
.expect("Call to `set_a` failed");

// In production code, you want to poll the transaction status.
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;

let a = contract
.get_a()
.call()
.await
.expect("Call to `get_a` failed");
println!("a after invoke: {:?}", a);
}
6 changes: 6 additions & 0 deletions src/bin/cli/args.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Cainome CLI arguments.
//!
use cainome_rs::ExecutionVersion;
use camino::Utf8PathBuf;
use clap::{Args, Parser};
use starknet::core::types::Felt;
Expand Down Expand Up @@ -56,6 +57,11 @@ pub struct CainomeArgs {
#[command(flatten)]
#[command(next_help_heading = "Plugins options")]
pub plugins: PluginOptions,

#[arg(long)]
#[arg(value_name = "EXECUTION_VERSION")]
#[arg(help = "The execution version to use. Supported values are 'v1', 'V1', 'v3', or 'V3'.")]
pub execution_version: ExecutionVersion,
}

#[derive(Debug, Args, Clone)]
Expand Down
1 change: 1 addition & 0 deletions src/bin/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async fn main() -> CainomeCliResult<()> {
pm.generate(PluginInput {
output_dir: args.output_dir,
contracts,
execution_version: args.execution_version,
})
.await?;

Expand Down
6 changes: 5 additions & 1 deletion src/bin/cli/plugins/builtins/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ impl BuiltinPlugin for RustPlugin {
.from_case(Case::Snake)
.to_case(Case::Pascal);

let expanded = cainome_rs::abi_to_tokenstream(&contract_name, &contract.tokens);
let expanded = cainome_rs::abi_to_tokenstream(
&contract_name,
&contract.tokens,
input.execution_version,
);
let filename = format!(
"{}.rs",
contract_name.from_case(Case::Pascal).to_case(Case::Snake)
Expand Down
Loading

0 comments on commit 6c82c5b

Please sign in to comment.