diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_crate_gen.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_crate_gen.rs index d92ec69aeb..71dc38d3f4 100644 --- a/framework/meta-lib/src/contract/generate_snippets/snippet_crate_gen.rs +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_crate_gen.rs @@ -2,11 +2,13 @@ use colored::Colorize; use std::{ fs::{self, File, OpenOptions}, io::Write, + path::Path, }; use crate::version_history; static SNIPPETS_SOURCE_FILE_NAME: &str = "interactor_main.rs"; +static LIB_SOURCE_FILE_NAME: &str = "interact.rs"; static SC_CONFIG_PATH: &str = "../sc-config.toml"; static CONFIG_TOML_PATH: &str = "config.toml"; static CONFIG_SOURCE_FILE_NAME: &str = "config.rs"; @@ -14,6 +16,8 @@ static FULL_PROXY_ENTRY: &str = r#"[[proxy]] path = "interactor/src/proxy.rs" "#; static PROXY_PATH: &str = "interactor/src/proxy.rs"; +static INTERACTOR_CS_TEST_FILE_NAME: &str = "interact_cs_tests.rs"; +static INTERACTOR_TEST_FILE_NAME: &str = "interact_tests.rs"; pub(crate) fn create_snippets_folder(snippets_folder_path: &str) { // returns error if folder already exists, so we ignore the result @@ -77,6 +81,9 @@ publish = false name = "rust-interact" path = "src/{SNIPPETS_SOURCE_FILE_NAME}" +[lib] +path = "src/{LIB_SOURCE_FILE_NAME}" + [dependencies.{contract_crate_name}] path = ".." @@ -91,9 +98,9 @@ clap = {{ version = "4.4.7", features = ["derive"] }} serde = {{ version = "1.0", features = ["derive"] }} toml = "0.8.6" -# uncomment when using chain simulator -# [features] -# chain-simulator-tests = [] +[features] +chain-simulator-tests = [] +blockchain-tests = [] "# ) .unwrap(); @@ -107,7 +114,7 @@ pub(crate) fn create_src_folder(snippets_folder_path: &str) { #[must_use] pub(crate) fn create_and_get_lib_file(snippets_folder_path: &str, overwrite: bool) -> File { - let lib_path = format!("{snippets_folder_path}/src/{SNIPPETS_SOURCE_FILE_NAME}"); + let lib_path = format!("{snippets_folder_path}/src/{LIB_SOURCE_FILE_NAME}"); if overwrite { File::create(&lib_path).unwrap() } else { @@ -125,6 +132,44 @@ pub(crate) fn create_and_get_lib_file(snippets_folder_path: &str, overwrite: boo } } +pub(crate) fn create_main_file(snippets_folder_path: &str, contract_crate_name: &str) { + let lib_path = format!("{snippets_folder_path}/src/{SNIPPETS_SOURCE_FILE_NAME}"); + + let mut file = File::create(lib_path).unwrap(); + + writeln!( + &mut file, + r#" +use multiversx_sc_snippets::imports::*; +use rust_interact::{contract_crate_name}_cli; + +#[tokio::main] +async fn main() {{ + {contract_crate_name}_cli().await; +}} +"# + ) + .unwrap(); +} + +pub(crate) fn create_test_folder_and_get_files(snippets_folder_path: &str) -> (File, File) { + let folder_path = format!("{snippets_folder_path}/tests"); + + if !Path::new(&folder_path).exists() { + fs::create_dir_all(&folder_path).expect("Failed to create tests directory"); + } + + let interactor_file_path = format!("{folder_path}/{INTERACTOR_TEST_FILE_NAME}"); + let interactor_cs_file_path = format!("{folder_path}/{INTERACTOR_CS_TEST_FILE_NAME}"); + + let interactor_file = + File::create(interactor_file_path).expect("Failed to create interact_tests.rs file"); + let interactor_cs_file = + File::create(interactor_cs_file_path).expect("Failed to create interact_cs_tests.rs file"); + + (interactor_file, interactor_cs_file) +} + pub(crate) fn create_sc_config_file(overwrite: bool) { // check if the file should be overwritten or if it already exists let mut file = if overwrite || !file_exists(SC_CONFIG_PATH) { diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_gen_main.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_gen_main.rs index c566dd49b6..1c77340c64 100644 --- a/framework/meta-lib/src/contract/generate_snippets/snippet_gen_main.rs +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_gen_main.rs @@ -8,13 +8,15 @@ use super::{ super::meta_config::MetaConfig, snippet_crate_gen::{ create_and_get_lib_file, create_config_rust_file, create_config_toml_file, - create_sc_config_file, create_snippets_cargo_toml, create_snippets_folder, - create_snippets_gitignore, create_src_folder, + create_main_file, create_sc_config_file, create_snippets_cargo_toml, + create_snippets_folder, create_snippets_gitignore, create_src_folder, + create_test_folder_and_get_files, }, snippet_sc_functions_gen::write_interact_struct_impl, snippet_template_gen::{ - write_config_constants, write_config_imports, write_config_struct_declaration, - write_config_struct_impl, write_interact_struct_declaration, write_snippet_constants, + write_chain_sim_test_to_file, write_config_constants, write_config_imports, + write_config_struct_declaration, write_config_struct_impl, + write_interact_struct_declaration, write_interactor_test_to_file, write_snippet_constants, write_snippet_imports, write_snippet_main_function, write_snippet_state_impl, write_state_struct_declaration, }, @@ -29,6 +31,13 @@ impl MetaConfig { write_snippets_to_file(&mut file, &self.original_contract_abi, crate_name); let mut config_file = create_config_and_get_file(&self.snippets_dir); write_config_to_file(&mut config_file); + let (mut interactor_test_file, mut chain_sim_test_file) = + create_test_folder_and_get_files(&self.snippets_dir); + write_tests_to_files( + &mut interactor_test_file, + &mut chain_sim_test_file, + crate_name, + ); } } @@ -43,6 +52,7 @@ fn create_snippets_crate_and_get_lib_file( create_snippets_cargo_toml(snippets_folder_path, contract_crate_name, overwrite); create_src_folder(snippets_folder_path); create_sc_config_file(overwrite); + create_main_file(snippets_folder_path, contract_crate_name); create_and_get_lib_file(snippets_folder_path, overwrite) } @@ -55,7 +65,7 @@ fn create_config_and_get_file(snippets_folder_path: &str) -> File { fn write_snippets_to_file(file: &mut File, abi: &ContractAbi, crate_name: &str) { write_snippet_imports(file); write_snippet_constants(file); - write_snippet_main_function(file, abi); + write_snippet_main_function(file, abi, crate_name); write_state_struct_declaration(file); write_snippet_state_impl(file); write_interact_struct_declaration(file); @@ -68,3 +78,12 @@ fn write_config_to_file(file: &mut File) { write_config_struct_declaration(file); write_config_struct_impl(file); } + +fn write_tests_to_files( + interactor_test_file: &mut File, + chain_sim_test_file: &mut File, + crate_name: &str, +) { + write_interactor_test_to_file(interactor_test_file, crate_name); + write_chain_sim_test_to_file(chain_sim_test_file, crate_name); +} diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_sc_functions_gen.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_sc_functions_gen.rs index b5789c9645..a883d9c3f1 100644 --- a/framework/meta-lib/src/contract/generate_snippets/snippet_sc_functions_gen.rs +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_sc_functions_gen.rs @@ -12,12 +12,18 @@ pub(crate) fn write_interact_struct_impl(file: &mut File, abi: &ContractAbi, cra writeln!( file, r#"impl ContractInteract {{ - async fn new() -> Self {{ + pub async fn new() -> Self {{ let config = Config::new(); - let mut interactor = Interactor::new(config.gateway_uri(), config.use_chain_simulator()).await; - interactor.set_current_dir_from_workspace("{}"); + let mut interactor = Interactor::new(config.gateway_uri()) + .await + .use_chain_simulator(config.use_chain_simulator()); + interactor.set_current_dir_from_workspace("{}"); let wallet_address = interactor.register_wallet(test_wallets::alice()).await; + + // Useful in the chain simulator setting + // generate blocks until ESDTSystemSCAddress is enabled + interactor.generate_blocks_until_epoch(1).await.unwrap(); let contract_code = BytesValue::interpret_from( {}, @@ -65,7 +71,6 @@ fn write_deploy_method_impl(file: &mut File, init_abi: &EndpointAbi, name: &Stri .init({}) .code(&self.contract_code) .returns(ReturnsNewAddress) - .run() .await; let new_address_bech32 = bech32::encode(&new_address); @@ -101,7 +106,6 @@ fn write_upgrade_endpoint_impl(file: &mut File, upgrade_abi: &EndpointAbi, name: .code(&self.contract_code) .code_metadata(CodeMetadata::UPGRADEABLE) .returns(ReturnsNewAddress) - .run() .await; @@ -132,7 +136,7 @@ fn write_endpoint_impl(file: &mut File, endpoint_abi: &EndpointAbi, name: &Strin } fn write_method_declaration(file: &mut File, endpoint_name: &str) { - writeln!(file, " async fn {endpoint_name}(&mut self) {{").unwrap(); + writeln!(file, " pub async fn {endpoint_name}(&mut self) {{").unwrap(); } fn write_payments_declaration(file: &mut File, accepted_tokens: &[String]) { @@ -214,7 +218,6 @@ fn write_contract_call(file: &mut File, endpoint_abi: &EndpointAbi, name: &Strin .typed(proxy::{}Proxy) .{}({}){} .returns(ReturnsResultUnmanaged) - .run() .await; @@ -237,7 +240,6 @@ fn write_contract_query(file: &mut File, endpoint_abi: &EndpointAbi, name: &Stri .typed(proxy::{}Proxy) .{}({}) .returns(ReturnsResultUnmanaged) - .run() .await; diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_template_gen.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_template_gen.rs index 95ecbd64df..d3bd3e91c4 100644 --- a/framework/meta-lib/src/contract/generate_snippets/snippet_template_gen.rs +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_template_gen.rs @@ -9,8 +9,8 @@ pub(crate) fn write_snippet_imports(file: &mut File) { file, "#![allow(non_snake_case)] -mod proxy; mod config; +mod proxy; use config::Config; use multiversx_sc_snippets::imports::*; @@ -18,8 +18,7 @@ use serde::{{Deserialize, Serialize}}; use std::{{ io::{{Read, Write}}, path::Path, -}}; -" +}};" ) .unwrap(); @@ -28,15 +27,13 @@ use std::{{ pub(crate) fn write_snippet_constants(file: &mut File) { writeln!(file, "const STATE_FILE: &str = \"state.toml\";").unwrap(); - - write_newline(file); } -pub(crate) fn write_snippet_main_function(file: &mut File, abi: &ContractAbi) { +pub(crate) fn write_snippet_main_function(file: &mut File, abi: &ContractAbi, crate_name: &str) { writeln!( file, - "#[tokio::main] -async fn main() {{ + " +pub async fn {crate_name}_cli() {{ env_logger::init(); let mut args = std::env::args(); @@ -76,14 +73,12 @@ async fn main() {{ }}" ) .unwrap(); - - write_newline(file); } pub(crate) fn write_interact_struct_declaration(file: &mut File) { writeln!( file, - "struct ContractInteract {{ + "pub struct ContractInteract {{ interactor: Interactor, wallet_address: Address, contract_code: BytesValue, @@ -100,7 +95,7 @@ pub(crate) fn write_state_struct_declaration(file: &mut File) { file, " #[derive(Debug, Default, Serialize, Deserialize)] -struct State {{ +pub struct State {{ contract_address: Option }}" ) @@ -162,8 +157,6 @@ use std::io::Read; " ) .unwrap(); - - write_newline(file); } pub(crate) fn write_config_constants(file: &mut File) { @@ -174,8 +167,6 @@ const CONFIG_FILE: &str = \"config.toml\"; " ) .unwrap(); - - write_newline(file); } pub(crate) fn write_config_struct_declaration(file: &mut File) { @@ -186,19 +177,17 @@ pub(crate) fn write_config_struct_declaration(file: &mut File) { pub enum ChainType {{ Real, Simulator, - }} +}} /// Contract Interact configuration #[derive(Debug, Deserialize)] pub struct Config {{ pub gateway_uri: String, pub chain_type: ChainType, - }} +}} "# ) .unwrap(); - - write_newline(file); } pub(crate) fn write_config_struct_impl(file: &mut File) { @@ -217,7 +206,7 @@ pub(crate) fn write_config_struct_impl(file: &mut File) { Config {{ gateway_uri: "http://localhost:8085".to_owned(), chain_type: ChainType::Simulator, - }} + }} }} // Returns the gateway URI @@ -230,12 +219,49 @@ pub(crate) fn write_config_struct_impl(file: &mut File) { match self.chain_type {{ ChainType::Real => false, ChainType::Simulator => true, + }} }} - }} - }} -"# +}}"# ) .unwrap(); +} - write_newline(file); +pub(crate) fn write_chain_sim_test_to_file(file: &mut File, crate_name: &str) { + writeln!( + file, + r#"use multiversx_sc_snippets::imports::*; +use rust_interact::ContractInteract; + +// Simple deploy test that runs using the chain simulator configuration. +// In order for this test to work, make sure that the `config.toml` file contains the chain simulator config (or choose it manually) +// The chain simulator should already be installed and running before attempting to run this test. +// The chain-simulator-tests feature should be present in Cargo.toml. +// Can be run with `sc-meta test -c`. +#[tokio::test] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn deploy_test_{crate_name}_cs() {{ + let mut interactor = ContractInteract::new().await; + + interactor.deploy().await; +}}"# + ).unwrap() +} + +pub(crate) fn write_interactor_test_to_file(file: &mut File, crate_name: &str) { + writeln!( + file, + r#"use multiversx_sc_snippets::imports::*; +use rust_interact::ContractInteract; + +// Simple deploy test that runs on the real blockchain configuration. +// In order for this test to work, make sure that the `config.toml` file contains the real blockchain config (or choose it manually) +// Can be run with `sc-meta test`. +#[tokio::test] +#[cfg_attr(not(feature = "blockchain-tests"), ignore)] +async fn deploy_test_{crate_name}() {{ + let mut interactor = ContractInteract::new().await; + + interactor.deploy().await; +}}"# + ).unwrap() }