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: add execution v1 or v3 as a parameter #42

Merged
merged 2 commits into from
Jul 5, 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
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
Loading