diff --git a/framework/base/src/macros.rs b/framework/base/src/macros.rs index 159b639195..9060e8f5d3 100644 --- a/framework/base/src/macros.rs +++ b/framework/base/src/macros.rs @@ -158,8 +158,8 @@ macro_rules! sc_format { /// Equivalent to the `?` operator for SCResult. #[deprecated( - since = "0.16.0", - note = "The `?` operator can now be used on `SCResult`, please use it instead." +since = "0.16.0", +note = "The `?` operator can now be used on `SCResult`, please use it instead." )] #[macro_export] macro_rules! sc_try { @@ -190,8 +190,8 @@ macro_rules! sc_try { /// # } /// ``` #[deprecated( - since = "0.26.0", - note = "Replace with the `#[only_owner]` attribute that can be placed on an endpoint. That one is more compact and shows up in the ABI." +since = "0.26.0", +note = "Replace with the `#[only_owner]` attribute that can be placed on an endpoint. That one is more compact and shows up in the ABI." )] #[macro_export] macro_rules! only_owner { @@ -209,3 +209,47 @@ macro_rules! non_zero_usize { NonZeroUsize::new($input).unwrap_or_else(|| sc_panic!($error_msg)) }; } + +#[macro_export] +macro_rules! endpoints_proxy { + ($endpoint_name:ident, $address:ident) + => { + multiversx_sc::types::ContractCallNoPayment::new( + $address, + stringify!($endpoint_name), + ); + }; +} + +#[macro_export] +macro_rules! constructors_proxy { + ($opt_address:ident) + => { + multiversx_sc::types::new_contract_deploy( + $opt_address, + ); + }; +} + +#[macro_export] +macro_rules! extract_opt_address { + ($address:expr) => { + { + core::mem::replace( + &mut $address.address, + multiversx_sc::types::ManagedOption::none(), + ) + } + }; +} + +#[macro_export] +macro_rules! extract_address { + ($address:expr) + => { + { + multiversx_sc::extract_opt_address!($address) + .unwrap_or_sc_panic(multiversx_sc::err_msg::RECIPIENT_ADDRESS_NOT_SET) + } + }; +} \ No newline at end of file diff --git a/framework/meta/src/cli_args/cli_args_contract.rs b/framework/meta/src/cli_args/cli_args_contract.rs index 1853d64675..8c337ba989 100644 --- a/framework/meta/src/cli_args/cli_args_contract.rs +++ b/framework/meta/src/cli_args/cli_args_contract.rs @@ -66,6 +66,18 @@ pub enum ContractCliAction { about = "Generates a snippets project, based on the contract ABI." )] GenerateSnippets(GenerateSnippetsArgs), + + #[command( + name = "proxy", + about = "Generates a proxy in trait" + )] + GenerateProxies, + + #[command( + name = "proxy-struct", + about = "Generates a proxy in struct" + )] + GenerateProxiesStruct } impl CliArgsToRaw for ContractCliAction { @@ -97,6 +109,12 @@ impl CliArgsToRaw for ContractCliAction { raw.push("snippets".to_string()); raw.append(&mut args.to_raw()); }, + ContractCliAction::GenerateProxies => { + raw.push("proxy".to_string()); + }, + ContractCliAction::GenerateProxiesStruct => { + raw.push("proxy-struct".to_string()); + } } raw } diff --git a/framework/meta/src/cmd/contract.rs b/framework/meta/src/cmd/contract.rs index 215c394d63..184a2a0f9c 100644 --- a/framework/meta/src/cmd/contract.rs +++ b/framework/meta/src/cmd/contract.rs @@ -1,3 +1,5 @@ +mod generate_proxy_trait; +mod generate_proxy_struct; mod generate_snippets; mod meta_abi; mod meta_config; @@ -31,6 +33,8 @@ pub fn cli_main() { ContractCliAction::GenerateSnippets(gs_args) => { meta_config_opt.generate_rust_snippets(&gs_args) }, + ContractCliAction::GenerateProxies => meta_config_opt.generate_rust_proxies(), + ContractCliAction::GenerateProxiesStruct => meta_config_opt.generate_rust_proxies_struct() } } diff --git a/framework/meta/src/cmd/contract/generate_proxy_struct.rs b/framework/meta/src/cmd/contract/generate_proxy_struct.rs new file mode 100644 index 0000000000..6544fc11fd --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_struct.rs @@ -0,0 +1,3 @@ +pub mod proxy_struct_gen_main; +pub mod proxy_struct_template_gen; +pub mod proxy_struct_sc_functions_gen; diff --git a/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_gen_main.rs b/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_gen_main.rs new file mode 100644 index 0000000000..c8f5a8fa0f --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_gen_main.rs @@ -0,0 +1,32 @@ +use std::fs::File; + +use multiversx_sc::abi::ContractAbi; + +use crate::cmd::contract::generate_proxy_trait::proxy_trait_crate_gen::create_and_get_lib_file; + +use super::{ + proxy_struct_sc_functions_gen::write_content, + proxy_struct_template_gen::{write_imports, write_struct_template}, + super::meta_config::MetaConfig, +}; + +static PROXIES_SOURCE_FILE_NAME: &str = "proxies_struct_interactor_main.rs"; + +impl MetaConfig { + pub fn generate_rust_proxies_struct(&self) { + let file = create_and_get_lib_file(PROXIES_SOURCE_FILE_NAME); + write_proxies_struct_to_file( + file, + &self.original_contract_abi, + ); + } +} + +fn write_proxies_struct_to_file( + mut file: File, + abi: &ContractAbi, +) { + write_imports(&mut file); + write_struct_template(&mut file); + write_content(&mut file, abi); +} diff --git a/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_sc_functions_gen.rs b/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_sc_functions_gen.rs new file mode 100644 index 0000000000..d1e900fc91 --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_sc_functions_gen.rs @@ -0,0 +1,140 @@ +use std::{fs::File, io::Write}; + +use multiversx_sc::abi::{ContractAbi, EndpointAbi, InputAbi, OutputAbis}; + +use crate::cmd::contract::generate_snippets::snippet_gen_common::write_newline; +use crate::cmd::contract::generate_snippets::snippet_sc_functions_gen::map_output_types_to_rust_types; +use crate::cmd::contract::generate_snippets::snippet_type_map::{handle_abi_type, RustTypeString}; + +pub(crate) fn write_content( + file: &mut File, + abi: &ContractAbi, +) +{ + for constructor_abi in &abi.constructors { + write_endpoint(file, constructor_abi, "ContractDeploy"); + write_constructor_content_macro(file); + write_constructor_contract_deploy(file, &constructor_abi.inputs); + writeln!(file, "\t\t___contract_deploy___").unwrap(); + writeln!(file, "\t}}").unwrap(); + write_newline(file); + } + + for endpoint_abi in &abi.endpoints { + write_endpoint(file, endpoint_abi, "ContractCallNoPayment"); + write_endpoint_content_macro(file, endpoint_abi.name); + write_contract_call(file, &endpoint_abi.inputs); + writeln!(file, "\t\t___contract_call___").unwrap(); + writeln!(file, "\t}}").unwrap(); + write_newline(file); + } + + writeln!(file, "}}").unwrap(); +} + +fn write_constructor_contract_deploy(file: &mut File, inputs: &[InputAbi]) { + if inputs.is_empty() { + return; + } + + for input in inputs.iter() { + write_constructor_contract_call(file, &input.arg_name); + } +} + +fn write_contract_call(file: &mut File, inputs: &[InputAbi]) { + if inputs.is_empty() { + return; + } + + for input in inputs.iter() { + write_contract_call_input(file, &input.arg_name); + } +} + +fn write_contract_call_input(file: &mut File, arg_name: &&str) { + writeln!(file, "\t\tContractCall::proxy_arg(&mut ___contract_call___, &{arg_name});").unwrap(); +} + +fn write_constructor_contract_call(file: &mut File, arg_name: &&str) { + writeln!(file, "\t\t___contract_deploy___.push_endpoint_arg(&{arg_name});").unwrap(); +} + +fn write_endpoint_content_macro(file: &mut File, name: &str) { + writeln!(file, "\t\tlet ___address___ = multiversx_sc::extract_address!(self);").unwrap(); + writeln!(file, "\t\tlet mut ___contract_call___ = multiversx_sc::endpoints_proxy!({name}, ___address___);").unwrap(); +} + +fn write_constructor_content_macro(file: &mut File) { + writeln!(file, "\t\tlet ___opt_address___ = multiversx_sc::extract_opt_address!(self);").unwrap(); + writeln!(file, "\t\tlet mut ___contract_deploy___ = multiversx_sc::constructors_proxy!(___opt_address___);").unwrap(); +} + +fn write_endpoint(file: &mut File, endpoint_abi: &EndpointAbi, interaction_deploy: &str) { + write_info_endpoint(file, endpoint_abi.docs); + write_endpoint_fn(file, endpoint_abi.rust_method_name); + write_generic_args(file, &endpoint_abi.inputs); + write_parameters(file, &endpoint_abi.inputs, interaction_deploy); + write_output(file, &endpoint_abi.outputs); +} + +fn write_output(file: &mut File, outputs: &OutputAbis) { + let output_type = map_output_types_to_rust_types(outputs); + + let output_type_print = output_type.replace("", ""); + write!(file, "{output_type_print}", ).unwrap(); + writeln!(file, "> {{").unwrap(); +} + +fn write_parameters(file: &mut File, inputs: &[InputAbi], interaction_deploy: &str) { + writeln!(file, "(").unwrap(); + writeln!(file, "\t\t&mut self,").unwrap(); + + for (index, input) in inputs.iter().enumerate() { + write_parameter_arg(file, index, &input.arg_name); + } + + write!(file, "\t) -> {interaction_deploy}").unwrap(); +} + +fn write_argument(file: &mut File, index: usize, type_name: String) { + let mut type_string = RustTypeString::default(); + handle_abi_type(&mut type_string, type_name); + let type_string_str = type_string.get_type_name().to_string(); + + let type_print = type_string_str.replace("", ""); + + writeln!(file, "\t\tArg{index}: multiversx_sc::codec::CodecInto<{}>,", type_print).unwrap(); +} diff --git a/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_template_gen.rs b/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_template_gen.rs new file mode 100644 index 0000000000..a33d14a0c4 --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_struct/proxy_struct_template_gen.rs @@ -0,0 +1,31 @@ +use std::{fs::File, io::Write}; + +use crate::cmd::contract::generate_snippets::snippet_gen_common::write_newline; + +pub(crate) fn write_imports(file: &mut File) { + writeln!( + file, + r#"#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] +multiversx_sc::imports!();"# + ) + .unwrap(); + + write_newline(file); +} + +pub(crate) fn write_struct_template(file: &mut File) { + write!( + file, + "pub struct Proxy +where + A: multiversx_sc::api::VMApi + 'static, +{{ + pub address: ManagedOption>, +}} + +impl Proxy +where + A: multiversx_sc::api::VMApi + 'static, +{{").unwrap(); +} diff --git a/framework/meta/src/cmd/contract/generate_proxy_trait.rs b/framework/meta/src/cmd/contract/generate_proxy_trait.rs new file mode 100644 index 0000000000..1a589cfd73 --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_trait.rs @@ -0,0 +1,4 @@ +pub mod proxy_trait_crate_gen; +pub mod proxy_trait_gen_main; +pub mod proxy_trait_template_gen; +pub mod proxy_trait_sc_functions_gen; \ No newline at end of file diff --git a/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_crate_gen.rs b/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_crate_gen.rs new file mode 100644 index 0000000000..3fb5c19510 --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_crate_gen.rs @@ -0,0 +1,10 @@ +use std::fs::File; + +#[must_use] +pub(crate) fn create_and_get_lib_file(proxies_file_name: &str) -> File { + let lib_path = format!("../{proxies_file_name}"); + match File::options().create_new(true).write(true).open(&lib_path) { + Ok(f) => f, + Err(_) => panic!("{lib_path} file already exists, --overwrite option for proxies was not provided"), + } +} diff --git a/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_gen_main.rs b/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_gen_main.rs new file mode 100644 index 0000000000..ca4e9da8ba --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_gen_main.rs @@ -0,0 +1,30 @@ +use std::fs::File; + +use multiversx_sc::abi::ContractAbi; + +use super::{ + proxy_trait_crate_gen::create_and_get_lib_file, + proxy_trait_sc_functions_gen::write_state_struct_impl, + proxy_trait_template_gen::write_proxy_imports, + super::meta_config::MetaConfig, +}; + +static PROXIES_SOURCE_FILE_NAME: &str = "proxies_trait_interactor_main.rs"; + +impl MetaConfig { + pub fn generate_rust_proxies(&self) { + let file = create_and_get_lib_file(PROXIES_SOURCE_FILE_NAME); + write_proxies_to_file( + file, + &self.original_contract_abi, + ); + } +} + +fn write_proxies_to_file( + mut file: File, + abi: &ContractAbi, +) { + write_proxy_imports(&mut file, abi.name); + write_state_struct_impl(&mut file, abi); +} diff --git a/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_sc_functions_gen.rs b/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_sc_functions_gen.rs new file mode 100644 index 0000000000..55b5b26b14 --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_sc_functions_gen.rs @@ -0,0 +1,112 @@ +use std::{fs::File, io::Write}; + +use multiversx_sc::abi::{ContractAbi, EndpointAbi, InputAbi, OutputAbi}; + +use crate::cmd::contract::generate_snippets::snippet_gen_common::write_newline; + +pub(crate) fn write_state_struct_impl( + file: &mut File, + abi: &ContractAbi, +) { + for constructor_abi in &abi.constructors { + write_endpoint_impl(file, constructor_abi); + write_newline(file); + } + + for endpoint_abi in &abi.endpoints { + write_endpoint_impl(file, endpoint_abi); + write_newline(file); + } + + // close impl block brackets + writeln!(file, "}}").unwrap(); +} + +fn write_endpoint_impl(file: &mut File, endpoint_abi: &EndpointAbi) { + write_method_attribute(file, endpoint_abi.name, endpoint_abi.rust_method_name); + write_method_declaration(file, endpoint_abi.rust_method_name); + write_comma(file, &endpoint_abi.inputs); + write_endpoint_args_declaration(file, &endpoint_abi.inputs); + write_endpoint_output(file, &endpoint_abi.outputs); + write_newline(file); +} + +fn write_comma(file: &mut File, inputs: &[InputAbi]) { + if !inputs.is_empty() { + write!( + file, + ", ", + ) + .unwrap(); + } +} + +fn write_method_attribute(file: &mut File, endpoint_name: &str, endpoint_method_name: &str) { + if endpoint_name != endpoint_method_name { + writeln!(file, " #[view({endpoint_name})]").unwrap(); + } else { + writeln!(file, " #[endpoint]").unwrap(); + } +} + +fn write_method_declaration(file: &mut File, endpoint_name: &str) { + write!(file, " fn {endpoint_name}(&mut self").unwrap(); +} + +fn write_endpoint_output(file: &mut File, outputs: &[OutputAbi]) { + if outputs.is_empty() { + write!( + file, + ";", + ) + .unwrap(); + return; + } + + for output in outputs { + write!( + file, + " -> {}", + output.type_name + ) + .unwrap(); + } + + write!( + file, + ";", + ) + .unwrap(); +} + +pub fn write_endpoint_args_declaration(file: &mut File, inputs: &[InputAbi]) { + if inputs.is_empty() { + write!( + file, + ")", + ) + .unwrap(); + return; + } + + for (index, input) in inputs.iter().enumerate() { + write!( + file, + "{}: {}", + input.arg_name, + input.type_name.to_string() + ) + .unwrap(); + + // Add a comma after each input except the last one + if index < inputs.len() - 1 { + write!(file, ", ").unwrap(); + } + } + + write!( + file, + ")", + ) + .unwrap(); +} \ No newline at end of file diff --git a/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_template_gen.rs b/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_template_gen.rs new file mode 100644 index 0000000000..37e4861447 --- /dev/null +++ b/framework/meta/src/cmd/contract/generate_proxy_trait/proxy_trait_template_gen.rs @@ -0,0 +1,13 @@ +use std::{fs::File, io::Write}; + +pub(crate) fn write_proxy_imports(file: &mut File, abi_name: &str) { + write!( + file, + "multiversx_sc::imports!(); + +#[multiversx_sc::proxy] +pub trait {abi_name} {{ +" + ) + .unwrap(); +} \ No newline at end of file diff --git a/framework/meta/src/cmd/contract/generate_snippets/snippet_sc_functions_gen.rs b/framework/meta/src/cmd/contract/generate_snippets/snippet_sc_functions_gen.rs index 94b0cc8a43..6bc0fe01de 100644 --- a/framework/meta/src/cmd/contract/generate_snippets/snippet_sc_functions_gen.rs +++ b/framework/meta/src/cmd/contract/generate_snippets/snippet_sc_functions_gen.rs @@ -205,7 +205,7 @@ fn write_contract_query(file: &mut File, endpoint_abi: &EndpointAbi) { .unwrap(); } -fn map_output_types_to_rust_types(outputs: &[OutputAbi]) -> String { +pub fn map_output_types_to_rust_types(outputs: &[OutputAbi]) -> String { let results_len = outputs.len(); if results_len == 0 { return "()".to_string(); diff --git a/framework/meta/src/cmd/contract/generate_snippets/snippet_type_map.rs b/framework/meta/src/cmd/contract/generate_snippets/snippet_type_map.rs index 803f8c6b29..5e6e28423b 100644 --- a/framework/meta/src/cmd/contract/generate_snippets/snippet_type_map.rs +++ b/framework/meta/src/cmd/contract/generate_snippets/snippet_type_map.rs @@ -183,7 +183,7 @@ fn get_abi_type(abi_type_str: &str) -> AbiType { } } -fn handle_abi_type(type_string: &mut RustTypeString, abi_type_str: String) { +pub fn handle_abi_type(type_string: &mut RustTypeString, abi_type_str: String) { let abi_type = get_abi_type(&abi_type_str); match abi_type { AbiType::UserDefined(user_type) => { diff --git a/framework/wasm-adapter/src/lib.rs b/framework/wasm-adapter/src/lib.rs index bf7e6797d0..20d0b4088e 100644 --- a/framework/wasm-adapter/src/lib.rs +++ b/framework/wasm-adapter/src/lib.rs @@ -12,4 +12,4 @@ pub mod api; pub mod error_hook; pub mod panic; pub mod wasm_alloc; -mod wasm_macros; +mod wasm_macros; \ No newline at end of file