diff --git a/contracts/Makefile b/contracts/Makefile index 85b241a..61bcf7c 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -19,6 +19,7 @@ generate_artifacts: jq .abi ${scarb_build}simple_interface${sierra} > ${artifacts}simple_interface.abi.json jq .abi ${scarb_build}structs${sierra} > ${artifacts}structs.abi.json jq .abi ${scarb_build}byte_array${sierra} > ${artifacts}byte_array.abi.json + jq .abi ${scarb_build}gen${sierra} > ${artifacts}gen.abi.json generate_rust: scarb build @@ -50,11 +51,14 @@ setup_byte_array: # starkli declare target/dev/contracts_basic.sierra.json ${config} # starkli deploy ${class_hash} --salt 0x1234 ${config} -# # Declare and deploy the basic contract on katana. -# setup_gen: -# $(eval class_hash=$(shell starkli class-hash target/dev/contracts_gen.sierra.json)) -# starkli declare target/dev/contracts_gen.sierra.json ${config} -# starkli deploy ${class_hash} --salt 0x1234 ${config} +# Declare and deploy the basic contract on katana. +setup_gen: + @set -x; \ + scarb build; \ + class_hash=$$(starkli class-hash ${scarb_build}gen${sierra}); \ + starkli declare ${scarb_build}gen${sierra} ${config}; \ + sleep 2; \ + starkli deploy "$${class_hash}" --salt 0x1234 ${config} # # Declare and deploy the event contract on katana. # setup_event: diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index 7df1135..e100361 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -11,3 +11,4 @@ mod abicov { mod simple_get_set; mod basic; +mod gen; diff --git a/crates/rs-macro/src/lib.rs b/crates/rs-macro/src/lib.rs index d3bcbf4..1981783 100644 --- a/crates/rs-macro/src/lib.rs +++ b/crates/rs-macro/src/lib.rs @@ -33,6 +33,7 @@ fn abigen_internal(input: TokenStream) -> TokenStream { &contract_name.to_string(), &abi_tokens, contract_abi.execution_version, + &contract_abi.derives, ); if let Some(out_path) = contract_abi.output_path { @@ -61,6 +62,7 @@ fn abigen_internal_legacy(input: TokenStream) -> TokenStream { &contract_name.to_string(), &abi_tokens, cainome_rs::ExecutionVersion::V1, + &[], ); if let Some(out_path) = contract_abi.output_path { diff --git a/crates/rs-macro/src/macro_inputs.rs b/crates/rs-macro/src/macro_inputs.rs index df6a08a..a1ac08e 100644 --- a/crates/rs-macro/src/macro_inputs.rs +++ b/crates/rs-macro/src/macro_inputs.rs @@ -37,6 +37,7 @@ pub(crate) struct ContractAbi { pub output_path: Option, pub type_aliases: HashMap, pub execution_version: ExecutionVersion, + pub derives: Vec, } impl Parse for ContractAbi { @@ -89,6 +90,7 @@ impl Parse for ContractAbi { let mut output_path: Option = None; let mut execution_version = ExecutionVersion::V1; let mut type_aliases = HashMap::new(); + let mut derives = Vec::new(); loop { if input.parse::().is_err() { @@ -135,6 +137,15 @@ impl Parse for ContractAbi { syn::Error::new(content.span(), format!("Invalid execution version: {}", e)) })?; } + "derives" => { + let content; + parenthesized!(content in input); + let parsed = content.parse_terminated(Spanned::::parse, Token![,])?; + + for derive in parsed { + derives.push(derive.to_token_stream().to_string()); + } + } _ => panic!("unexpected named parameter `{}`", name), } } @@ -145,6 +156,7 @@ impl Parse for ContractAbi { output_path, type_aliases, execution_version, + derives, }) } } diff --git a/crates/rs/src/expand/enum.rs b/crates/rs/src/expand/enum.rs index 7f1ba66..a61542a 100644 --- a/crates/rs/src/expand/enum.rs +++ b/crates/rs/src/expand/enum.rs @@ -9,7 +9,7 @@ use crate::expand::utils; pub struct CairoEnum; impl CairoEnum { - pub fn expand_decl(composite: &Composite) -> TokenStream2 { + pub fn expand_decl(composite: &Composite, derives: &[String]) -> TokenStream2 { if composite.is_builtin() { return quote!(); } @@ -29,6 +29,12 @@ impl CairoEnum { } } + let mut internal_derives = vec![]; + + for d in derives { + internal_derives.push(utils::str_to_type(d)); + } + if composite.is_generic() { let gen_args: Vec = composite .generic_args @@ -42,18 +48,15 @@ impl CairoEnum { // Add one phantom for each generic type. // Those phantom fields are ignored by serde. - // TODO: as for struct, we need to have a better way for the user to specify the - // traits to derive. - quote! { - #[derive(Debug, PartialEq, PartialOrd, Clone, serde::Serialize, serde::Deserialize)] + #[derive(#(#internal_derives,)*)] pub enum #enum_name<#(#gen_args),*> { #(#variants),* } } } else { quote! { - #[derive(Debug, PartialEq, PartialOrd, Clone, serde::Serialize, serde::Deserialize)] + #[derive(#(#internal_derives,)*)] pub enum #enum_name { #(#variants),* } diff --git a/crates/rs/src/expand/struct.rs b/crates/rs/src/expand/struct.rs index c77edd5..606d410 100644 --- a/crates/rs/src/expand/struct.rs +++ b/crates/rs/src/expand/struct.rs @@ -9,7 +9,7 @@ use crate::expand::utils; pub struct CairoStruct; impl CairoStruct { - pub fn expand_decl(composite: &Composite) -> TokenStream2 { + pub fn expand_decl(composite: &Composite, derives: &[String]) -> TokenStream2 { if composite.is_builtin() { return quote!(); } @@ -35,6 +35,12 @@ impl CairoStruct { } } + let mut internal_derives = vec![]; + + for d in derives { + internal_derives.push(utils::str_to_type(d)); + } + if composite.is_generic() { let gen_args: Vec = composite .generic_args @@ -48,18 +54,15 @@ impl CairoStruct { // Add one phantom for each generic type. // Those phantom fields are ignored by serde. - // TODO: add a way for the user to specify which trait must be derived for the - // generated structs. For now Serde is used to ensure easy serialization. - quote! { - #[derive(Debug, PartialEq, PartialOrd, Clone, serde::Serialize, serde::Deserialize)] + #[derive(#(#internal_derives,)*)] pub struct #struct_name<#(#gen_args),*> { #(pub #members),* } } } else { quote! { - #[derive(Debug, PartialEq, PartialOrd, Clone, serde::Serialize, serde::Deserialize)] + #[derive(#(#internal_derives,)*)] pub struct #struct_name { #(pub #members),* } diff --git a/crates/rs/src/lib.rs b/crates/rs/src/lib.rs index 73e478e..6e3ce63 100644 --- a/crates/rs/src/lib.rs +++ b/crates/rs/src/lib.rs @@ -67,6 +67,8 @@ pub struct Abigen { pub types_aliases: HashMap, /// The version of transaction to be executed. pub execution_version: ExecutionVersion, + /// Derives to be added to the generated types. + pub derives: Vec, } impl Abigen { @@ -83,6 +85,7 @@ impl Abigen { abi_source: Utf8PathBuf::from(abi_source), types_aliases: HashMap::new(), execution_version: ExecutionVersion::V1, + derives: vec![], } } @@ -112,8 +115,12 @@ impl Abigen { match AbiParser::tokens_from_abi_string(&file_content, &self.types_aliases) { Ok(tokens) => { - let expanded = - abi_to_tokenstream(&self.contract_name, &tokens, self.execution_version); + let expanded = abi_to_tokenstream( + &self.contract_name, + &tokens, + self.execution_version, + &self.derives, + ); Ok(ContractBindings { name: self.contract_name.clone(), @@ -140,6 +147,7 @@ pub fn abi_to_tokenstream( contract_name: &str, abi_tokens: &TokenizedAbi, execution_version: ExecutionVersion, + derives: &[String], ) -> TokenStream2 { let contract_name = utils::str_to_ident(contract_name); @@ -149,13 +157,13 @@ pub fn abi_to_tokenstream( for s in &abi_tokens.structs { let s_composite = s.to_composite().expect("composite expected"); - tokens.push(CairoStruct::expand_decl(s_composite)); + tokens.push(CairoStruct::expand_decl(s_composite, derives)); tokens.push(CairoStruct::expand_impl(s_composite)); } for e in &abi_tokens.enums { let e_composite = e.to_composite().expect("composite expected"); - tokens.push(CairoEnum::expand_decl(e_composite)); + tokens.push(CairoEnum::expand_decl(e_composite, derives)); tokens.push(CairoEnum::expand_impl(e_composite)); tokens.push(CairoEnumEvent::expand( diff --git a/examples/structs.rs b/examples/structs.rs new file mode 100644 index 0000000..deeac0b --- /dev/null +++ b/examples/structs.rs @@ -0,0 +1,22 @@ +use cainome::rs::abigen; + +use starknet::core::types::Felt; + +abigen!( + MyContract, + "./contracts/abi/gen.abi.json", + derives(Debug, Clone) +); + +#[tokio::main] +async fn main() { + let s = MyStruct:: { + f1: Felt::ONE, + f2: Felt::TWO, + f3: Felt::THREE, + }; + + println!("{:?}", s); + + let _s2 = s.clone(); +} diff --git a/src/bin/cli/args.rs b/src/bin/cli/args.rs index 5adc1ce..adb4b25 100644 --- a/src/bin/cli/args.rs +++ b/src/bin/cli/args.rs @@ -62,6 +62,11 @@ pub struct CainomeArgs { #[arg(value_name = "EXECUTION_VERSION")] #[arg(help = "The execution version to use. Supported values are 'v1', 'V1', 'v3', or 'V3'.")] pub execution_version: ExecutionVersion, + + #[arg(long)] + #[arg(value_name = "DERIVES")] + #[arg(help = "Derives to be added to the generated types.")] + pub derives: Option>, } #[derive(Debug, Args, Clone)] diff --git a/src/bin/cli/main.rs b/src/bin/cli/main.rs index 43ca2da..7786c6d 100644 --- a/src/bin/cli/main.rs +++ b/src/bin/cli/main.rs @@ -52,6 +52,7 @@ async fn main() -> CainomeCliResult<()> { output_dir: args.output_dir, contracts, execution_version: args.execution_version, + derives: args.derives.unwrap_or_default(), }) .await?; diff --git a/src/bin/cli/plugins/builtins/rust.rs b/src/bin/cli/plugins/builtins/rust.rs index 458b83e..ddbb0cd 100644 --- a/src/bin/cli/plugins/builtins/rust.rs +++ b/src/bin/cli/plugins/builtins/rust.rs @@ -36,6 +36,7 @@ impl BuiltinPlugin for RustPlugin { &contract_name, &contract.tokens, input.execution_version, + &input.derives, ); let filename = format!( "{}.rs", diff --git a/src/bin/cli/plugins/mod.rs b/src/bin/cli/plugins/mod.rs index 0c7cb6a..3c79e43 100644 --- a/src/bin/cli/plugins/mod.rs +++ b/src/bin/cli/plugins/mod.rs @@ -13,6 +13,7 @@ pub struct PluginInput { pub output_dir: Utf8PathBuf, pub contracts: Vec, pub execution_version: ExecutionVersion, + pub derives: Vec, } #[derive(Debug)]