From 57b1e4937485763a3c902bc4d22ba20085570977 Mon Sep 17 00:00:00 2001 From: Mihai Calin Luca Date: Mon, 10 Feb 2025 18:02:10 +0100 Subject: [PATCH 1/5] first working implementation --- framework/base/Cargo.toml | 16 +- framework/base/src/abi.rs | 3 +- framework/base/src/abi/build_info_abi.rs | 6 +- framework/base/src/abi/endpoint_abi.rs | 11 +- framework/base/src/abi/esdt_attribute_abi.rs | 3 +- framework/base/src/abi/event_abi.rs | 4 +- framework/base/src/abi/type_description.rs | 11 +- .../src/abi/type_description_container.rs | 2 +- .../src/contract/generate_snippets.rs | 1 + .../generate_snippets/snippet_abi_check.rs | 282 ++++++++++++++++++ .../generate_snippets/snippet_crate_gen.rs | 2 +- .../generate_snippets/snippet_gen_main.rs | 44 ++- .../snippet_sc_functions_gen.rs | 31 +- .../generate_snippets/snippet_template_gen.rs | 10 +- 14 files changed, 376 insertions(+), 50 deletions(-) create mode 100644 framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs diff --git a/framework/base/Cargo.toml b/framework/base/Cargo.toml index 7b58312fd4..540f725a22 100644 --- a/framework/base/Cargo.toml +++ b/framework/base/Cargo.toml @@ -4,7 +4,10 @@ version = "0.56.0" edition = "2021" rust-version = "1.78" -authors = ["Andrei Marinica ", "MultiversX "] +authors = [ + "Andrei Marinica ", + "MultiversX ", +] license = "GPL-3.0-only" readme = "README.md" repository = "https://github.com/multiversx/mx-sdk-rs" @@ -12,7 +15,12 @@ homepage = "https://multiversx.com/" documentation = "https://docs.multiversx.com/" description = "MultiversX smart contract API" keywords = ["multiversx", "wasm", "webassembly", "blockchain", "contract"] -categories = ["no-std", "wasm", "cryptography::cryptocurrencies", "development-tools"] +categories = [ + "no-std", + "wasm", + "cryptography::cryptocurrencies", + "development-tools", +] [package.metadata.docs.rs] all-features = true @@ -28,6 +36,10 @@ hex-literal = "=0.4.1" bitflags = "=2.8.0" num-traits = { version = "=0.2.19", default-features = false } unwrap-infallible = "0.1.5" +serde = { version = "1.0.217", features = [ + "derive", + "alloc", +], default-features = false } [dependencies.multiversx-sc-derive] version = "=0.56.0" diff --git a/framework/base/src/abi.rs b/framework/base/src/abi.rs index dd128161bc..42862f4140 100644 --- a/framework/base/src/abi.rs +++ b/framework/base/src/abi.rs @@ -19,6 +19,7 @@ pub use contract_abi::*; pub use endpoint_abi::*; pub use esdt_attribute_abi::EsdtAttributeAbi; pub use event_abi::*; +use serde::Deserialize; pub use type_abi::*; pub use type_abi_from::*; pub use type_description::*; @@ -26,7 +27,7 @@ pub use type_description_container::*; pub type TypeName = alloc::string::String; -#[derive(Clone, Default, Debug, PartialEq, Eq)] +#[derive(Clone, Default, Debug, PartialEq, Eq, Deserialize)] pub struct TypeNames { pub abi: alloc::string::String, pub rust: alloc::string::String, diff --git a/framework/base/src/abi/build_info_abi.rs b/framework/base/src/abi/build_info_abi.rs index 917cfa6b5a..6f76a724e0 100644 --- a/framework/base/src/abi/build_info_abi.rs +++ b/framework/base/src/abi/build_info_abi.rs @@ -1,13 +1,13 @@ /// Deisgned to hold metadata of the contract crate. /// Must be instanced inside the smart contract crate to work, /// that is why a `create` associated method would not make sense here. -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq)] pub struct BuildInfoAbi { pub contract_crate: ContractCrateBuildAbi, pub framework: FrameworkBuildAbi, } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq)] pub struct ContractCrateBuildAbi { pub name: &'static str, pub version: &'static str, @@ -16,7 +16,7 @@ pub struct ContractCrateBuildAbi { /// Gives the multiversx-sc metadata. /// Should be instanced via the `create` associated function. -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq)] pub struct FrameworkBuildAbi { pub name: &'static str, pub version: &'static str, diff --git a/framework/base/src/abi/endpoint_abi.rs b/framework/base/src/abi/endpoint_abi.rs index 8e96554d84..39021e7001 100644 --- a/framework/base/src/abi/endpoint_abi.rs +++ b/framework/base/src/abi/endpoint_abi.rs @@ -4,15 +4,16 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; +use serde::Deserialize; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct InputAbi { pub arg_name: String, pub type_names: TypeNames, pub multi_arg: bool, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct OutputAbi { pub output_name: String, pub type_names: TypeNames, @@ -21,7 +22,7 @@ pub struct OutputAbi { pub type OutputAbis = Vec; -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq, Deserialize)] pub enum EndpointMutabilityAbi { #[default] Mutable, @@ -29,7 +30,7 @@ pub enum EndpointMutabilityAbi { Pure, } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq, Deserialize)] pub enum EndpointTypeAbi { #[default] Init, @@ -38,7 +39,7 @@ pub enum EndpointTypeAbi { PromisesCallback, } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq, Deserialize)] pub struct EndpointAbi { pub docs: Vec, pub name: String, diff --git a/framework/base/src/abi/esdt_attribute_abi.rs b/framework/base/src/abi/esdt_attribute_abi.rs index ee37466064..6e3a26dc06 100644 --- a/framework/base/src/abi/esdt_attribute_abi.rs +++ b/framework/base/src/abi/esdt_attribute_abi.rs @@ -1,8 +1,9 @@ use alloc::string::{String, ToString}; +use serde::Deserialize; use super::{TypeAbi, TypeDescriptionContainerImpl, TypeName}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct EsdtAttributeAbi { pub ticker: String, pub ty: TypeName, diff --git a/framework/base/src/abi/event_abi.rs b/framework/base/src/abi/event_abi.rs index 416e967b75..f40e895add 100644 --- a/framework/base/src/abi/event_abi.rs +++ b/framework/base/src/abi/event_abi.rs @@ -4,14 +4,14 @@ use alloc::{ vec::Vec, }; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct EventInputAbi { pub arg_name: String, pub type_name: TypeName, pub indexed: bool, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct EventAbi { pub docs: Vec, pub identifier: String, diff --git a/framework/base/src/abi/type_description.rs b/framework/base/src/abi/type_description.rs index f7e78ccef7..3b120c87c9 100644 --- a/framework/base/src/abi/type_description.rs +++ b/framework/base/src/abi/type_description.rs @@ -2,10 +2,11 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; +use serde::Deserialize; use super::TypeNames; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct TypeDescription { pub docs: Vec, pub names: TypeNames, @@ -46,7 +47,7 @@ impl TypeDescription { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub enum TypeContents { NotSpecified, Enum(Vec), @@ -60,7 +61,7 @@ impl TypeContents { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] pub struct EnumVariantDescription { pub docs: Vec, pub name: String, @@ -87,7 +88,7 @@ impl EnumVariantDescription { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] pub struct StructFieldDescription { pub docs: Vec, pub name: String, @@ -110,7 +111,7 @@ impl StructFieldDescription { /// This makes it easier for humans to read readable in the transaction output. /// /// It cannot have data fields, only simple enums allowed. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct ExplicitEnumVariantDescription { pub docs: Vec, pub name: String, diff --git a/framework/base/src/abi/type_description_container.rs b/framework/base/src/abi/type_description_container.rs index 16dda2c8b9..173f50aafc 100644 --- a/framework/base/src/abi/type_description_container.rs +++ b/framework/base/src/abi/type_description_container.rs @@ -17,7 +17,7 @@ pub trait TypeDescriptionContainer { fn insert_all(&mut self, other: &Self); } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq, Deserialize)] pub struct TypeDescriptionContainerImpl(pub Vec<(TypeNames, TypeDescription)>); impl TypeDescriptionContainer for TypeDescriptionContainerImpl { diff --git a/framework/meta-lib/src/contract/generate_snippets.rs b/framework/meta-lib/src/contract/generate_snippets.rs index 9d7fc1ebeb..90262d31cf 100644 --- a/framework/meta-lib/src/contract/generate_snippets.rs +++ b/framework/meta-lib/src/contract/generate_snippets.rs @@ -1,3 +1,4 @@ +pub mod snippet_abi_check; pub mod snippet_crate_gen; pub mod snippet_gen_common; pub mod snippet_gen_main; diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs new file mode 100644 index 0000000000..353ae6f78d --- /dev/null +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs @@ -0,0 +1,282 @@ +use std::fs::File; +use std::io::Write; + +use multiversx_sc::abi::{ContractAbi, EndpointAbi, EndpointMutabilityAbi, InputAbi}; +use serde::Deserialize; + +use crate::abi_json::{serialize_abi_to_json, ContractAbiJson}; + +use super::snippet_crate_gen::LIB_SOURCE_FILE_NAME; +use super::snippet_sc_functions_gen::endpoint_args_when_called; +use super::snippet_type_map::map_abi_type_to_rust_type; +use crate::contract::generate_snippets::snippet_sc_functions_gen::DEFAULT_GAS; + +#[derive(PartialEq, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ShortContractAbi { + #[serde(default)] + pub name: String, + #[serde(default)] + pub constructors: Vec, + #[serde(default, rename = "upgradeConstructor")] + pub upgrade_constructors: Vec, + pub endpoints: Vec, +} + +impl From for ShortContractAbi { + fn from(value: ContractAbi) -> Self { + Self { + name: value.name, + constructors: value.constructors, + upgrade_constructors: value.upgrade_constructors, + endpoints: value.endpoints, + } + } +} + +const PREV_ABI_NAME: &str = "prev-abi.json"; + +pub(crate) fn check_abi_differences( + current_contract_abi: &ShortContractAbi, + snippets_dir: &String, + overwrite: bool, +) -> ShortContractAbi { + if !overwrite { + let prev_abi_path = format!("{}/prev-abi.json", snippets_dir); + if let Ok(prev_abi_content) = std::fs::read_to_string(&prev_abi_path) { + if let Ok(prev_abi) = serde_json::from_str::(&prev_abi_content) { + let mut diff_abi = ShortContractAbi { + name: current_contract_abi.name.clone(), + constructors: vec![], + upgrade_constructors: vec![], + endpoints: vec![], + }; + + // changed and new constructors + for constructor in ¤t_contract_abi.constructors { + if !prev_abi.constructors.contains(constructor) { + diff_abi.constructors.push(constructor.clone()); + } + } + + // changed and new upgrade constructors + for upgrade_constructor in ¤t_contract_abi.upgrade_constructors { + if !prev_abi.upgrade_constructors.contains(upgrade_constructor) { + diff_abi + .upgrade_constructors + .push(upgrade_constructor.clone()); + } + } + + // changed and new endpoints + for endpoint in ¤t_contract_abi.endpoints { + if !prev_abi.endpoints.contains(endpoint) { + diff_abi.endpoints.push(endpoint.clone()); + } + } + + // deleted endpoints + for endpoint in &prev_abi.endpoints { + if !current_contract_abi.endpoints.contains(endpoint) { + diff_abi.endpoints.retain(|e| e.name != endpoint.name); + } + } + + return diff_abi; + } + } + } + current_contract_abi.clone() +} + +pub(crate) fn create_prev_abi_file(snippets_dir: &String, contract_abi: &ContractAbi) { + let abi_json = ContractAbiJson::from(contract_abi); + let abi_string = serialize_abi_to_json(&abi_json); + + let abi_file_path = format!("{snippets_dir}/{PREV_ABI_NAME}"); + let mut abi_file = File::create(abi_file_path).unwrap(); + write!(abi_file, "{abi_string}").unwrap(); +} + +pub(crate) fn add_new_endpoints_to_file(snippets_dir: &String, diff_abi: &ShortContractAbi) { + let interact_lib_path = format!("{snippets_dir}/{LIB_SOURCE_FILE_NAME}"); + let file_content = std::fs::read_to_string(&interact_lib_path).unwrap(); + let mut updated_content = file_content.clone(); + + for endpoint_abi in &diff_abi.endpoints { + updated_content = + insert_or_replace_function(&updated_content, endpoint_abi, &diff_abi.name); + } + + for constructor in &diff_abi.constructors { + updated_content = insert_or_replace_function(&updated_content, constructor, &diff_abi.name); + } + + for upgrade_constructor in &diff_abi.upgrade_constructors { + updated_content = + insert_or_replace_function(&updated_content, upgrade_constructor, &diff_abi.name); + } + + std::fs::write(interact_lib_path, updated_content).unwrap(); +} + +fn insert_or_replace_function( + file_content: &str, + endpoint_abi: &EndpointAbi, + contract_name: &String, +) -> String { + let function_signature = format!("pub async fn {}", endpoint_abi.rust_method_name); + let mut updated_content = file_content.to_string(); + + let new_function = { + let mut function_buffer = String::new(); + write_endpoint_impl_to_string(&mut function_buffer, endpoint_abi, contract_name); + function_buffer + }; + + if let Some(start) = file_content.find(&function_signature) { + // remove existing function + let mut balance = 0; + let mut end = start; + for (i, c) in file_content[start..].char_indices() { + match c { + '{' => balance += 1, + '}' => { + balance -= 1; + if balance == 0 { + end = start + i + 1; + break; + } + }, + _ => {}, + } + } + updated_content.replace_range(start..end, &new_function); + } else { + // append new function + updated_content.push_str("\n\n"); + updated_content.push_str(&new_function); + } + + updated_content +} + +pub(crate) fn write_endpoint_impl_to_string( + buffer: &mut String, + endpoint_abi: &EndpointAbi, + name: &String, +) { + write_method_declaration_to_string(buffer, &endpoint_abi.rust_method_name); + write_payments_declaration_to_string(buffer, &endpoint_abi.payable_in_tokens); + write_endpoint_args_declaration_to_string(buffer, &endpoint_abi.inputs); + + if matches!(endpoint_abi.mutability, EndpointMutabilityAbi::Readonly) { + write_contract_query_to_string(buffer, endpoint_abi, name); + } else { + write_contract_call_to_string(buffer, endpoint_abi, name); + } + + buffer.push_str(" }\n"); + buffer.push('\n'); +} + +pub(crate) fn write_method_declaration_to_string(buffer: &mut String, endpoint_name: &str) { + buffer.push_str(&format!(" pub async fn {endpoint_name}(&mut self) {{\n")); +} + +pub(crate) fn write_payments_declaration_to_string( + buffer: &mut String, + accepted_tokens: &[String], +) { + if accepted_tokens.is_empty() { + return; + } + + let biguint_default = map_abi_type_to_rust_type("BigUint".to_string()); + let first_accepted = &accepted_tokens[0]; + + if first_accepted == "EGLD" { + buffer.push_str(&format!( + " let egld_amount = {};\n", + biguint_default.get_default_value_expr() + )); + } else { + buffer.push_str( + " let token_id = String::new(); + let token_nonce = 0u64; + let token_amount = ", + ); + buffer.push_str(&biguint_default.get_default_value_expr()); + buffer.push_str(";\n"); + } + + buffer.push('\n'); +} + +fn write_endpoint_args_declaration_to_string(buffer: &mut String, inputs: &[InputAbi]) { + if inputs.is_empty() { + return; + } + + for input in inputs { + let rust_type = map_abi_type_to_rust_type(input.type_names.abi.clone()); + buffer.push_str(&format!( + " let {} = {};\n", + input.arg_name, + rust_type.get_default_value_expr() + )); + } + + buffer.push('\n'); +} + +fn write_contract_call_to_string(buffer: &mut String, endpoint_abi: &EndpointAbi, name: &String) { + let payment_snippet = if endpoint_abi.payable_in_tokens.is_empty() { + "".to_string() + } else if endpoint_abi.payable_in_tokens[0] == "EGLD" { + "\n .egld(egld_amount)".to_string() + } else { + "\n .payment((TokenIdentifier::from(token_id.as_str()), token_nonce, token_amount))".to_string() + }; + + buffer.push_str(&format!( + r#" let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_address()) + .gas({DEFAULT_GAS}) + .typed(proxy::{}Proxy) + .{}({}){} + .returns(ReturnsResultUnmanaged) + .run() + .await; + + println!("Result: {{response:?}}"); +"#, + name, + endpoint_abi.rust_method_name, + endpoint_args_when_called(endpoint_abi.inputs.as_slice()), + payment_snippet, + )); +} + +fn write_contract_query_to_string(buffer: &mut String, endpoint_abi: &EndpointAbi, name: &String) { + buffer.push_str(&format!( + r#" let result_value = self + .interactor + .query() + .to(self.state.current_address()) + .typed(proxy::{}Proxy) + .{}({}) + .returns(ReturnsResultUnmanaged) + .run() + .await; + + println!("Result: {{result_value:?}}"); +"#, + name, + endpoint_abi.rust_method_name, + endpoint_args_when_called(endpoint_abi.inputs.as_slice()), + )); +} 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 f5676f2e7e..97dac746dd 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 @@ -8,7 +8,7 @@ use std::{ use crate::version_history; static SNIPPETS_SOURCE_FILE_NAME: &str = "interactor_main.rs"; -static LIB_SOURCE_FILE_NAME: &str = "interact.rs"; +pub(crate) 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"; 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 5642acf6a8..01748a6af2 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 @@ -1,11 +1,12 @@ use std::fs::File; -use multiversx_sc::abi::ContractAbi; - use crate::cli::GenerateSnippetsArgs; use super::{ super::meta_config::MetaConfig, + snippet_abi_check::{ + add_new_endpoints_to_file, check_abi_differences, create_prev_abi_file, ShortContractAbi, + }, snippet_crate_gen::{ create_and_get_lib_file, create_config_rust_file, create_config_toml_file, create_main_file, create_sc_config_file, create_snippets_cargo_toml, @@ -26,18 +27,31 @@ impl MetaConfig { pub fn generate_rust_snippets(&self, args: &GenerateSnippetsArgs) { let main_contract = self.sc_config.main_contract(); let crate_name = &main_contract.contract_name.replace("-", "_"); - let mut file = - create_snippets_crate_and_get_lib_file(&self.snippets_dir, crate_name, args.overwrite); - 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, - ); + let original_contract_abi = ShortContractAbi::from(self.original_contract_abi.clone()); + let diff_abi = + check_abi_differences(&original_contract_abi, &self.snippets_dir, args.overwrite); + if &diff_abi == &original_contract_abi { + let mut file = create_snippets_crate_and_get_lib_file( + &self.snippets_dir, + crate_name, + args.overwrite, + ); + write_snippets_to_file(&mut file, &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, + ); + } else { + add_new_endpoints_to_file(&self.snippets_dir, &diff_abi); + } + + // create prev-abi.json file + create_prev_abi_file(&self.snippets_dir, &self.original_contract_abi); } } @@ -62,7 +76,7 @@ fn create_config_and_get_file(snippets_folder_path: &str) -> File { create_config_rust_file(snippets_folder_path) } -fn write_snippets_to_file(file: &mut File, abi: &ContractAbi, crate_name: &str) { +fn write_snippets_to_file(file: &mut File, abi: &ShortContractAbi, crate_name: &str) { write_snippet_imports(file); write_snippet_constants(file); write_snippet_main_function(file, abi, 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 fa37d40b1e..0b8fac4e16 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 @@ -1,12 +1,19 @@ use std::{fs::File, io::Write}; -use multiversx_sc::abi::{ContractAbi, EndpointAbi, EndpointMutabilityAbi, InputAbi}; +use multiversx_sc::abi::{EndpointAbi, EndpointMutabilityAbi, InputAbi}; -use super::{snippet_gen_common::write_newline, snippet_type_map::map_abi_type_to_rust_type}; +use super::{ + snippet_abi_check::ShortContractAbi, snippet_gen_common::write_newline, + snippet_type_map::map_abi_type_to_rust_type, +}; -const DEFAULT_GAS: &str = "30_000_000u64"; +pub(crate) const DEFAULT_GAS: &str = "30_000_000u64"; -pub(crate) fn write_interact_struct_impl(file: &mut File, abi: &ContractAbi, crate_name: &str) { +pub(crate) fn write_interact_struct_impl( + file: &mut File, + abi: &ShortContractAbi, + crate_name: &str, +) { let crate_path = crate_name.replace("_", "-"); let wasm_output_file_path_expr = format!("\"mxsc:../output/{crate_path}.mxsc.json\""); @@ -55,7 +62,7 @@ pub(crate) fn write_interact_struct_impl(file: &mut File, abi: &ContractAbi, cra writeln!(file, "}}").unwrap(); } -fn write_deploy_method_impl(file: &mut File, init_abi: &EndpointAbi, name: &String) { +pub(crate) fn write_deploy_method_impl(file: &mut File, init_abi: &EndpointAbi, name: &String) { write_method_declaration(file, "deploy"); write_endpoint_args_declaration(file, &init_abi.inputs); let proxy_name = format!("{}Proxy", name); @@ -88,7 +95,11 @@ fn write_deploy_method_impl(file: &mut File, init_abi: &EndpointAbi, name: &Stri write_newline(file); } -fn write_upgrade_endpoint_impl(file: &mut File, upgrade_abi: &EndpointAbi, name: &String) { +pub(crate) fn write_upgrade_endpoint_impl( + file: &mut File, + upgrade_abi: &EndpointAbi, + name: &String, +) { write_method_declaration(file, "upgrade"); write_endpoint_args_declaration(file, &upgrade_abi.inputs); let proxy_name = format!("{}Proxy", name); @@ -120,7 +131,7 @@ fn write_upgrade_endpoint_impl(file: &mut File, upgrade_abi: &EndpointAbi, name: write_newline(file); } -fn write_endpoint_impl(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { +pub(crate) fn write_endpoint_impl(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { write_method_declaration(file, &endpoint_abi.rust_method_name); write_payments_declaration(file, &endpoint_abi.payable_in_tokens); write_endpoint_args_declaration(file, &endpoint_abi.inputs); @@ -135,11 +146,11 @@ fn write_endpoint_impl(file: &mut File, endpoint_abi: &EndpointAbi, name: &Strin write_newline(file); } -fn write_method_declaration(file: &mut File, endpoint_name: &str) { +pub(crate) fn write_method_declaration(file: &mut File, endpoint_name: &str) { writeln!(file, " pub async fn {endpoint_name}(&mut self) {{").unwrap(); } -fn write_payments_declaration(file: &mut File, accepted_tokens: &[String]) { +pub(crate) fn write_payments_declaration(file: &mut File, accepted_tokens: &[String]) { if accepted_tokens.is_empty() { return; } @@ -187,7 +198,7 @@ fn write_endpoint_args_declaration(file: &mut File, inputs: &[InputAbi]) { write_newline(file); } -fn endpoint_args_when_called(inputs: &[InputAbi]) -> String { +pub(crate) fn endpoint_args_when_called(inputs: &[InputAbi]) -> String { let mut result = String::new(); for input in inputs { if !result.is_empty() { 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 75dfe258a5..76e23e9e7f 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 @@ -1,8 +1,6 @@ use std::{fs::File, io::Write}; -use multiversx_sc::abi::ContractAbi; - -use super::snippet_gen_common::write_newline; +use super::{snippet_abi_check::ShortContractAbi, snippet_gen_common::write_newline}; pub(crate) fn write_snippet_imports(file: &mut File) { writeln!( @@ -29,7 +27,11 @@ pub(crate) fn write_snippet_constants(file: &mut File) { writeln!(file, "const STATE_FILE: &str = \"state.toml\";").unwrap(); } -pub(crate) fn write_snippet_main_function(file: &mut File, abi: &ContractAbi, crate_name: &str) { +pub(crate) fn write_snippet_main_function( + file: &mut File, + abi: &ShortContractAbi, + crate_name: &str, +) { writeln!( file, " From db1df153fcd3f4f6ab34a09164eec281ec14988e Mon Sep 17 00:00:00 2001 From: Mihai Calin Luca Date: Wed, 19 Feb 2025 02:43:51 +0100 Subject: [PATCH 2/5] solved deserializing issue, still more generation issues to be solved --- .../generate_snippets/snippet_abi_check.rs | 196 ++++++-- .../generate_snippets/snippet_gen_main.rs | 2 +- .../snippet_sc_functions_gen.rs | 423 +++++++++--------- .../generate_snippets/snippet_template_gen.rs | 2 +- 4 files changed, 384 insertions(+), 239 deletions(-) diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs index 353ae6f78d..cb629d7210 100644 --- a/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs @@ -1,35 +1,141 @@ use std::fs::File; use std::io::Write; -use multiversx_sc::abi::{ContractAbi, EndpointAbi, EndpointMutabilityAbi, InputAbi}; -use serde::Deserialize; +use multiversx_sc::abi::{ContractAbi, EndpointAbi, InputAbi, OutputAbi}; +use serde::de::DeserializeOwned; +use serde::{de, Deserialize, Deserializer}; use crate::abi_json::{serialize_abi_to_json, ContractAbiJson}; use super::snippet_crate_gen::LIB_SOURCE_FILE_NAME; -use super::snippet_sc_functions_gen::endpoint_args_when_called; use super::snippet_type_map::map_abi_type_to_rust_type; use crate::contract::generate_snippets::snippet_sc_functions_gen::DEFAULT_GAS; -#[derive(PartialEq, Deserialize, Clone)] +#[derive(PartialEq, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub(crate) struct ShortContractAbi { #[serde(default)] pub name: String, + #[serde(default, deserialize_with = "deserialize_single_or_vec")] + pub constructor: Vec, + #[serde( + default, + rename = "upgradeConstructor", + deserialize_with = "deserialize_single_or_vec" + )] + pub upgrade_constructor: Vec, #[serde(default)] - pub constructors: Vec, - #[serde(default, rename = "upgradeConstructor")] - pub upgrade_constructors: Vec, - pub endpoints: Vec, + pub endpoints: Vec, +} + +#[derive(PartialEq, Deserialize, Clone, Debug)] +pub(crate) struct ShortEndpointAbi { + #[serde(default)] + pub name: String, + #[serde(default)] + pub mutability: String, + #[serde(default, skip_deserializing)] + pub rust_method_name: String, + #[serde(default)] + pub payable_in_tokens: Vec, + #[serde(default)] + pub inputs: Vec, + #[serde(default)] + pub outputs: Vec, + #[serde(default)] + pub allow_multiple_var_args: bool, +} + +#[derive(PartialEq, Deserialize, Clone, Debug)] +pub(crate) struct ShortInputAbi { + #[serde(default)] + pub name: String, + #[serde(rename = "type")] + pub type_name: String, + #[serde(default)] + pub multi_arg: bool, +} + +#[derive(PartialEq, Deserialize, Clone, Debug)] +pub(crate) struct ShortOutputAbi { + #[serde(rename = "type")] + pub type_name: String, + #[serde(default)] + pub multi_result: bool, +} + +fn deserialize_single_or_vec<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: DeserializeOwned, +{ + let value = serde_json::Value::deserialize(deserializer)?; + match value { + serde_json::Value::Array(vec) => { + serde_json::from_value(serde_json::Value::Array(vec)).map_err(de::Error::custom) + }, + _ => Ok(serde_json::from_value(value.clone()) + .map(|single| vec![single]) + .expect(&format!("error at {value:?}"))), // .map_err(de::Error::custom), + } +} + +impl From for ShortEndpointAbi { + fn from(value: EndpointAbi) -> Self { + Self { + name: value.name, + mutability: format!("{:?}", value.mutability).to_lowercase(), + rust_method_name: value.rust_method_name, + payable_in_tokens: value.payable_in_tokens, + inputs: value.inputs.into_iter().map(ShortInputAbi::from).collect(), + outputs: value + .outputs + .into_iter() + .map(ShortOutputAbi::from) + .collect(), + allow_multiple_var_args: value.allow_multiple_var_args, + } + } +} + +impl From for ShortInputAbi { + fn from(value: InputAbi) -> Self { + Self { + name: value.arg_name, + type_name: value.type_names.abi, + multi_arg: value.multi_arg, + } + } +} + +impl From for ShortOutputAbi { + fn from(value: OutputAbi) -> Self { + Self { + type_name: value.type_names.abi, + multi_result: value.multi_result, + } + } } impl From for ShortContractAbi { fn from(value: ContractAbi) -> Self { Self { name: value.name, - constructors: value.constructors, - upgrade_constructors: value.upgrade_constructors, - endpoints: value.endpoints, + constructor: value + .constructors + .into_iter() + .map(ShortEndpointAbi::from) + .collect(), + upgrade_constructor: value + .upgrade_constructors + .into_iter() + .map(ShortEndpointAbi::from) + .collect(), + endpoints: value + .endpoints + .into_iter() + .map(ShortEndpointAbi::from) + .collect(), } } } @@ -47,23 +153,23 @@ pub(crate) fn check_abi_differences( if let Ok(prev_abi) = serde_json::from_str::(&prev_abi_content) { let mut diff_abi = ShortContractAbi { name: current_contract_abi.name.clone(), - constructors: vec![], - upgrade_constructors: vec![], + constructor: vec![], + upgrade_constructor: vec![], endpoints: vec![], }; // changed and new constructors - for constructor in ¤t_contract_abi.constructors { - if !prev_abi.constructors.contains(constructor) { - diff_abi.constructors.push(constructor.clone()); + for constructor in ¤t_contract_abi.constructor { + if !prev_abi.constructor.contains(constructor) { + diff_abi.constructor.push(constructor.clone()); } } // changed and new upgrade constructors - for upgrade_constructor in ¤t_contract_abi.upgrade_constructors { - if !prev_abi.upgrade_constructors.contains(upgrade_constructor) { + for upgrade_constructor in ¤t_contract_abi.upgrade_constructor { + if !prev_abi.upgrade_constructor.contains(upgrade_constructor) { diff_abi - .upgrade_constructors + .upgrade_constructor .push(upgrade_constructor.clone()); } } @@ -76,13 +182,17 @@ pub(crate) fn check_abi_differences( } // deleted endpoints + // bug here when deleting and diff with no overwrite for endpoint in &prev_abi.endpoints { if !current_contract_abi.endpoints.contains(endpoint) { diff_abi.endpoints.retain(|e| e.name != endpoint.name); } } + println!("diff_abi {diff_abi:?}"); return diff_abi; + } else { + println!("here") } } } @@ -99,7 +209,7 @@ pub(crate) fn create_prev_abi_file(snippets_dir: &String, contract_abi: &Contrac } pub(crate) fn add_new_endpoints_to_file(snippets_dir: &String, diff_abi: &ShortContractAbi) { - let interact_lib_path = format!("{snippets_dir}/{LIB_SOURCE_FILE_NAME}"); + let interact_lib_path = format!("{snippets_dir}/src/{LIB_SOURCE_FILE_NAME}"); let file_content = std::fs::read_to_string(&interact_lib_path).unwrap(); let mut updated_content = file_content.clone(); @@ -108,11 +218,11 @@ pub(crate) fn add_new_endpoints_to_file(snippets_dir: &String, diff_abi: &ShortC insert_or_replace_function(&updated_content, endpoint_abi, &diff_abi.name); } - for constructor in &diff_abi.constructors { + for constructor in &diff_abi.constructor { updated_content = insert_or_replace_function(&updated_content, constructor, &diff_abi.name); } - for upgrade_constructor in &diff_abi.upgrade_constructors { + for upgrade_constructor in &diff_abi.upgrade_constructor { updated_content = insert_or_replace_function(&updated_content, upgrade_constructor, &diff_abi.name); } @@ -120,9 +230,10 @@ pub(crate) fn add_new_endpoints_to_file(snippets_dir: &String, diff_abi: &ShortC std::fs::write(interact_lib_path, updated_content).unwrap(); } +// this may be buggy fn insert_or_replace_function( file_content: &str, - endpoint_abi: &EndpointAbi, + endpoint_abi: &ShortEndpointAbi, contract_name: &String, ) -> String { let function_signature = format!("pub async fn {}", endpoint_abi.rust_method_name); @@ -163,14 +274,14 @@ fn insert_or_replace_function( pub(crate) fn write_endpoint_impl_to_string( buffer: &mut String, - endpoint_abi: &EndpointAbi, + endpoint_abi: &ShortEndpointAbi, name: &String, ) { write_method_declaration_to_string(buffer, &endpoint_abi.rust_method_name); write_payments_declaration_to_string(buffer, &endpoint_abi.payable_in_tokens); write_endpoint_args_declaration_to_string(buffer, &endpoint_abi.inputs); - if matches!(endpoint_abi.mutability, EndpointMutabilityAbi::Readonly) { + if endpoint_abi.mutability == "readonly".to_string() { write_contract_query_to_string(buffer, endpoint_abi, name); } else { write_contract_call_to_string(buffer, endpoint_abi, name); @@ -206,23 +317,23 @@ pub(crate) fn write_payments_declaration_to_string( let token_nonce = 0u64; let token_amount = ", ); - buffer.push_str(&biguint_default.get_default_value_expr()); + buffer.push_str(biguint_default.get_default_value_expr()); buffer.push_str(";\n"); } buffer.push('\n'); } -fn write_endpoint_args_declaration_to_string(buffer: &mut String, inputs: &[InputAbi]) { +fn write_endpoint_args_declaration_to_string(buffer: &mut String, inputs: &[ShortInputAbi]) { if inputs.is_empty() { return; } for input in inputs { - let rust_type = map_abi_type_to_rust_type(input.type_names.abi.clone()); + let rust_type = map_abi_type_to_rust_type(input.type_name.clone()); buffer.push_str(&format!( " let {} = {};\n", - input.arg_name, + input.name, rust_type.get_default_value_expr() )); } @@ -230,7 +341,11 @@ fn write_endpoint_args_declaration_to_string(buffer: &mut String, inputs: &[Inpu buffer.push('\n'); } -fn write_contract_call_to_string(buffer: &mut String, endpoint_abi: &EndpointAbi, name: &String) { +fn write_contract_call_to_string( + buffer: &mut String, + endpoint_abi: &ShortEndpointAbi, + name: &String, +) { let payment_snippet = if endpoint_abi.payable_in_tokens.is_empty() { "".to_string() } else if endpoint_abi.payable_in_tokens[0] == "EGLD" { @@ -256,12 +371,16 @@ fn write_contract_call_to_string(buffer: &mut String, endpoint_abi: &EndpointAbi "#, name, endpoint_abi.rust_method_name, - endpoint_args_when_called(endpoint_abi.inputs.as_slice()), + endpoint_args_when_called_short(endpoint_abi.inputs.as_slice()), payment_snippet, )); } -fn write_contract_query_to_string(buffer: &mut String, endpoint_abi: &EndpointAbi, name: &String) { +fn write_contract_query_to_string( + buffer: &mut String, + endpoint_abi: &ShortEndpointAbi, + name: &String, +) { buffer.push_str(&format!( r#" let result_value = self .interactor @@ -277,6 +396,17 @@ fn write_contract_query_to_string(buffer: &mut String, endpoint_abi: &EndpointAb "#, name, endpoint_abi.rust_method_name, - endpoint_args_when_called(endpoint_abi.inputs.as_slice()), + endpoint_args_when_called_short(endpoint_abi.inputs.as_slice()), )); } + +pub(crate) fn endpoint_args_when_called_short(inputs: &[ShortInputAbi]) -> String { + let mut result = String::new(); + for input in inputs { + if !result.is_empty() { + result.push_str(", "); + } + result.push_str(&input.name); + } + result +} 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 01748a6af2..8e67d8d23d 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 @@ -30,7 +30,7 @@ impl MetaConfig { let original_contract_abi = ShortContractAbi::from(self.original_contract_abi.clone()); let diff_abi = check_abi_differences(&original_contract_abi, &self.snippets_dir, args.overwrite); - if &diff_abi == &original_contract_abi { + if diff_abi == original_contract_abi { let mut file = create_snippets_crate_and_get_lib_file( &self.snippets_dir, 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 0b8fac4e16..2b4a7f21b6 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 @@ -1,10 +1,11 @@ use std::{fs::File, io::Write}; -use multiversx_sc::abi::{EndpointAbi, EndpointMutabilityAbi, InputAbi}; +// use multiversx_sc::abi::{EndpointAbi, EndpointMutabilityAbi, InputAbi}; use super::{ - snippet_abi_check::ShortContractAbi, snippet_gen_common::write_newline, - snippet_type_map::map_abi_type_to_rust_type, + snippet_abi_check::{write_endpoint_impl_to_string, ShortContractAbi}, + // snippet_gen_common::write_newline, + // snippet_type_map::map_abi_type_to_rust_type, }; pub(crate) const DEFAULT_GAS: &str = "30_000_000u64"; @@ -48,216 +49,230 @@ pub(crate) fn write_interact_struct_impl( crate_path, wasm_output_file_path_expr, ) .unwrap(); - write_deploy_method_impl(file, &abi.constructors[0], &abi.name); - for upgrade_abi in &abi.upgrade_constructors { - write_upgrade_endpoint_impl(file, upgrade_abi, &abi.name); - } - - for endpoint_abi in &abi.endpoints { - write_endpoint_impl(file, endpoint_abi, &abi.name); - } - - // close impl block brackets - writeln!(file, "}}").unwrap(); -} - -pub(crate) fn write_deploy_method_impl(file: &mut File, init_abi: &EndpointAbi, name: &String) { - write_method_declaration(file, "deploy"); - write_endpoint_args_declaration(file, &init_abi.inputs); - let proxy_name = format!("{}Proxy", name); - - writeln!( - file, - r#" let new_address = self - .interactor - .tx() - .from(&self.wallet_address) - .gas({DEFAULT_GAS}) - .typed(proxy::{}) - .init({}) - .code(&self.contract_code) - .returns(ReturnsNewAddress) - .run() - .await; - let new_address_bech32 = bech32::encode(&new_address); - self.state - .set_address(Bech32Address::from_bech32_string(new_address_bech32.clone())); - - println!("new address: {{new_address_bech32}}");"#, - proxy_name, - endpoint_args_when_called(init_abi.inputs.as_slice()), - ) - .unwrap(); - - // close method block brackets - writeln!(file, " }}").unwrap(); - write_newline(file); -} - -pub(crate) fn write_upgrade_endpoint_impl( - file: &mut File, - upgrade_abi: &EndpointAbi, - name: &String, -) { - write_method_declaration(file, "upgrade"); - write_endpoint_args_declaration(file, &upgrade_abi.inputs); - let proxy_name = format!("{}Proxy", name); - - writeln!( - file, - r#" let response = self - .interactor - .tx() - .to(self.state.current_address()) - .from(&self.wallet_address) - .gas({DEFAULT_GAS}) - .typed(proxy::{}) - .upgrade({}) - .code(&self.contract_code) - .code_metadata(CodeMetadata::UPGRADEABLE) - .returns(ReturnsResultUnmanaged) - .run() - .await; - - println!("Result: {{response:?}}");"#, - proxy_name, - endpoint_args_when_called(upgrade_abi.inputs.as_slice()), - ) - .unwrap(); - - // close method block brackets - writeln!(file, " }}").unwrap(); - write_newline(file); -} + let mut buffer = String::new(); + write_endpoint_impl_to_string(&mut buffer, &abi.constructor[0], &abi.name); -pub(crate) fn write_endpoint_impl(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { - write_method_declaration(file, &endpoint_abi.rust_method_name); - write_payments_declaration(file, &endpoint_abi.payable_in_tokens); - write_endpoint_args_declaration(file, &endpoint_abi.inputs); - if matches!(endpoint_abi.mutability, EndpointMutabilityAbi::Readonly) { - write_contract_query(file, endpoint_abi, name); - } else { - write_contract_call(file, endpoint_abi, name); + for upgrade_abi in &abi.upgrade_constructor { + write_endpoint_impl_to_string(&mut buffer, upgrade_abi, &abi.name); } - // close method block brackets - writeln!(file, " }}").unwrap(); - write_newline(file); -} - -pub(crate) fn write_method_declaration(file: &mut File, endpoint_name: &str) { - writeln!(file, " pub async fn {endpoint_name}(&mut self) {{").unwrap(); -} - -pub(crate) fn write_payments_declaration(file: &mut File, accepted_tokens: &[String]) { - if accepted_tokens.is_empty() { - return; - } - - // only handle EGLD and "any" case, as they're the most common - let biguint_default = map_abi_type_to_rust_type("BigUint".to_string()); - let first_accepted = &accepted_tokens[0]; - if first_accepted == "EGLD" { - writeln!( - file, - " let egld_amount = {};", - biguint_default.get_default_value_expr() - ) - .unwrap(); - } else { - writeln!( - file, - " let token_id = String::new(); - let token_nonce = 0u64; - let token_amount = {};", - biguint_default.get_default_value_expr() - ) - .unwrap(); - } - - write_newline(file); -} - -fn write_endpoint_args_declaration(file: &mut File, inputs: &[InputAbi]) { - if inputs.is_empty() { - return; + for endpoint_abi in &abi.endpoints { + write_endpoint_impl_to_string(&mut buffer, endpoint_abi, &abi.name); } - for input in inputs { - let rust_type = map_abi_type_to_rust_type(input.type_names.abi.clone()); - writeln!( - file, - " let {} = {};", - input.arg_name, - rust_type.get_default_value_expr() - ) - .unwrap(); - } + write!(file, "{}", buffer).unwrap(); - write_newline(file); -} + // write_deploy_method_impl(file, &abi.constructor[0], &abi.name); -pub(crate) fn endpoint_args_when_called(inputs: &[InputAbi]) -> String { - let mut result = String::new(); - for input in inputs { - if !result.is_empty() { - result.push_str(", "); - } - result.push_str(&input.arg_name); - } - result -} + // for upgrade_abi in &abi.upgrade_constructor { + // write_upgrade_endpoint_impl(file, upgrade_abi, &abi.name); + // } -fn write_contract_call(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { - let payment_snippet = if endpoint_abi.payable_in_tokens.is_empty() { - "" - } else if endpoint_abi.payable_in_tokens[0] == "EGLD" { - "\n .egld(egld_amount)" - } else { - "\n .payment((TokenIdentifier::from(token_id.as_str()), token_nonce, token_amount))" - }; + // for endpoint_abi in &abi.endpoints { + // write_endpoint_impl(file, endpoint_abi, &abi.name); + // } - writeln!( - file, - r#" let response = self - .interactor - .tx() - .from(&self.wallet_address) - .to(self.state.current_address()) - .gas({DEFAULT_GAS}) - .typed(proxy::{}Proxy) - .{}({}){} - .returns(ReturnsResultUnmanaged) - .run() - .await; - - println!("Result: {{response:?}}");"#, - name, - endpoint_abi.rust_method_name, - endpoint_args_when_called(endpoint_abi.inputs.as_slice()), - payment_snippet, - ) - .unwrap(); + // close impl block brackets + writeln!(file, "}}").unwrap(); } -fn write_contract_query(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { - writeln!( - file, - r#" let result_value = self - .interactor - .query() - .to(self.state.current_address()) - .typed(proxy::{}Proxy) - .{}({}) - .returns(ReturnsResultUnmanaged) - .run() - .await; - - println!("Result: {{result_value:?}}");"#, - name, - endpoint_abi.rust_method_name, - endpoint_args_when_called(endpoint_abi.inputs.as_slice()), - ) - .unwrap(); -} +// pub(crate) fn write_deploy_method_impl(file: &mut File, init_abi: &EndpointAbi, name: &String) { +// write_method_declaration(file, "deploy"); +// write_endpoint_args_declaration(file, &init_abi.inputs); +// let proxy_name = format!("{}Proxy", name); + +// writeln!( +// file, +// r#" let new_address = self +// .interactor +// .tx() +// .from(&self.wallet_address) +// .gas({DEFAULT_GAS}) +// .typed(proxy::{}) +// .init({}) +// .code(&self.contract_code) +// .returns(ReturnsNewAddress) +// .run() +// .await; +// let new_address_bech32 = bech32::encode(&new_address); +// self.state +// .set_address(Bech32Address::from_bech32_string(new_address_bech32.clone())); + +// println!("new address: {{new_address_bech32}}");"#, +// proxy_name, +// endpoint_args_when_called(init_abi.inputs.as_slice()), +// ) +// .unwrap(); + +// // close method block brackets +// writeln!(file, " }}").unwrap(); +// write_newline(file); +// } + +// pub(crate) fn write_upgrade_endpoint_impl( +// file: &mut File, +// upgrade_abi: &EndpointAbi, +// name: &String, +// ) { +// write_method_declaration(file, "upgrade"); +// write_endpoint_args_declaration(file, &upgrade_abi.inputs); +// let proxy_name = format!("{}Proxy", name); + +// writeln!( +// file, +// r#" let response = self +// .interactor +// .tx() +// .to(self.state.current_address()) +// .from(&self.wallet_address) +// .gas({DEFAULT_GAS}) +// .typed(proxy::{}) +// .upgrade({}) +// .code(&self.contract_code) +// .code_metadata(CodeMetadata::UPGRADEABLE) +// .returns(ReturnsResultUnmanaged) +// .run() +// .await; + +// println!("Result: {{response:?}}");"#, +// proxy_name, +// endpoint_args_when_called(upgrade_abi.inputs.as_slice()), +// ) +// .unwrap(); + +// // close method block brackets +// writeln!(file, " }}").unwrap(); +// write_newline(file); +// } + +// pub(crate) fn write_endpoint_impl(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { +// write_method_declaration(file, &endpoint_abi.rust_method_name); +// write_payments_declaration(file, &endpoint_abi.payable_in_tokens); +// write_endpoint_args_declaration(file, &endpoint_abi.inputs); +// if matches!(endpoint_abi.mutability, EndpointMutabilityAbi::Readonly) { +// write_contract_query(file, endpoint_abi, name); +// } else { +// write_contract_call(file, endpoint_abi, name); +// } + +// // close method block brackets +// writeln!(file, " }}").unwrap(); +// write_newline(file); +// } + +// pub(crate) fn write_method_declaration(file: &mut File, endpoint_name: &str) { +// writeln!(file, " pub async fn {endpoint_name}(&mut self) {{").unwrap(); +// } + +// pub(crate) fn write_payments_declaration(file: &mut File, accepted_tokens: &[String]) { +// if accepted_tokens.is_empty() { +// return; +// } + +// // only handle EGLD and "any" case, as they're the most common +// let biguint_default = map_abi_type_to_rust_type("BigUint".to_string()); +// let first_accepted = &accepted_tokens[0]; +// if first_accepted == "EGLD" { +// writeln!( +// file, +// " let egld_amount = {};", +// biguint_default.get_default_value_expr() +// ) +// .unwrap(); +// } else { +// writeln!( +// file, +// " let token_id = String::new(); +// let token_nonce = 0u64; +// let token_amount = {};", +// biguint_default.get_default_value_expr() +// ) +// .unwrap(); +// } + +// write_newline(file); +// } + +// fn write_endpoint_args_declaration(file: &mut File, inputs: &[InputAbi]) { +// if inputs.is_empty() { +// return; +// } + +// for input in inputs { +// let rust_type = map_abi_type_to_rust_type(input.type_names.abi.clone()); +// writeln!( +// file, +// " let {} = {};", +// input.arg_name, +// rust_type.get_default_value_expr() +// ) +// .unwrap(); +// } + +// write_newline(file); +// } + +// pub(crate) fn endpoint_args_when_called(inputs: &[InputAbi]) -> String { +// let mut result = String::new(); +// for input in inputs { +// if !result.is_empty() { +// result.push_str(", "); +// } +// result.push_str(&input.arg_name); +// } +// result +// } + +// fn write_contract_call(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { +// let payment_snippet = if endpoint_abi.payable_in_tokens.is_empty() { +// "" +// } else if endpoint_abi.payable_in_tokens[0] == "EGLD" { +// "\n .egld(egld_amount)" +// } else { +// "\n .payment((TokenIdentifier::from(token_id.as_str()), token_nonce, token_amount))" +// }; + +// writeln!( +// file, +// r#" let response = self +// .interactor +// .tx() +// .from(&self.wallet_address) +// .to(self.state.current_address()) +// .gas({DEFAULT_GAS}) +// .typed(proxy::{}Proxy) +// .{}({}){} +// .returns(ReturnsResultUnmanaged) +// .run() +// .await; + +// println!("Result: {{response:?}}");"#, +// name, +// endpoint_abi.rust_method_name, +// endpoint_args_when_called(endpoint_abi.inputs.as_slice()), +// payment_snippet, +// ) +// .unwrap(); +// } + +// fn write_contract_query(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { +// writeln!( +// file, +// r#" let result_value = self +// .interactor +// .query() +// .to(self.state.current_address()) +// .typed(proxy::{}Proxy) +// .{}({}) +// .returns(ReturnsResultUnmanaged) +// .run() +// .await; + +// println!("Result: {{result_value:?}}");"#, +// name, +// endpoint_abi.rust_method_name, +// endpoint_args_when_called(endpoint_abi.inputs.as_slice()), +// ) +// .unwrap(); +// } 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 76e23e9e7f..2aa7b839b2 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 @@ -50,7 +50,7 @@ pub async fn {crate_name}_cli() {{ // all contracts have a deploy snippet writeln!(file, r#" "deploy" => interact.deploy().await,"#).unwrap(); - for upgrade_endpoint in &abi.upgrade_constructors { + for upgrade_endpoint in &abi.upgrade_constructor { writeln!( file, r#" "{}" => interact.{}().await,"#, From b7d0e78e8ff9059fb184e3bae57288f10347a91a Mon Sep 17 00:00:00 2001 From: Mihai Calin Luca Date: Thu, 27 Mar 2025 01:51:50 +0100 Subject: [PATCH 3/5] paths and cleanup --- .../generate_snippets/snippet_abi_check.rs | 97 ++++---- .../generate_snippets/snippet_gen_main.rs | 14 +- .../snippet_sc_functions_gen.rs | 218 +----------------- 3 files changed, 52 insertions(+), 277 deletions(-) diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs index bc549e7ee1..38b54cbc54 100644 --- a/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs @@ -1,5 +1,6 @@ use std::fs::File; use std::io::Write; +use std::path::Path; use multiversx_sc::abi::{ContractAbi, EndpointAbi, InputAbi, OutputAbi}; use serde::de::DeserializeOwned; @@ -11,6 +12,8 @@ use super::snippet_crate_gen::LIB_SOURCE_FILE_NAME; use super::snippet_type_map::map_abi_type_to_rust_type; use crate::contract::generate_snippets::snippet_sc_functions_gen::DEFAULT_GAS; +const PREV_ABI_NAME: &str = "prev-abi.json"; + #[derive(PartialEq, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub(crate) struct ShortContractAbi { @@ -140,76 +143,71 @@ impl From for ShortContractAbi { } } -const PREV_ABI_NAME: &str = "prev-abi.json"; - pub(crate) fn check_abi_differences( current_contract_abi: &ShortContractAbi, - snippets_dir: &String, + snippets_dir: &Path, overwrite: bool, ) -> ShortContractAbi { if !overwrite { - let prev_abi_path = format!("{}/prev-abi.json", snippets_dir); - if let Ok(prev_abi_content) = std::fs::read_to_string(&prev_abi_path) { - if let Ok(prev_abi) = serde_json::from_str::(&prev_abi_content) { - let mut diff_abi = ShortContractAbi { - name: current_contract_abi.name.clone(), - constructor: vec![], - upgrade_constructor: vec![], - endpoints: vec![], - }; - - // changed and new constructors - for constructor in ¤t_contract_abi.constructor { - if !prev_abi.constructor.contains(constructor) { - diff_abi.constructor.push(constructor.clone()); - } - } - - // changed and new upgrade constructors - for upgrade_constructor in ¤t_contract_abi.upgrade_constructor { - if !prev_abi.upgrade_constructor.contains(upgrade_constructor) { - diff_abi - .upgrade_constructor - .push(upgrade_constructor.clone()); - } - } + let prev_abi_path = snippets_dir.join(PREV_ABI_NAME); + let prev_abi_content = std::fs::read_to_string(&prev_abi_path) + .unwrap_or_else(|_| panic!("Failed to read file at path: {:?}", prev_abi_path)); + let prev_abi = serde_json::from_str::(&prev_abi_content) + .unwrap_or_else(|_| panic!("Failed to deserialize prev-abi.json content")); + + let mut diff_abi = ShortContractAbi { + name: current_contract_abi.name.clone(), + constructor: vec![], + upgrade_constructor: vec![], + endpoints: vec![], + }; + + // changed and new constructors + for constructor in ¤t_contract_abi.constructor { + if !prev_abi.constructor.contains(constructor) { + diff_abi.constructor.push(constructor.clone()); + } + } - // changed and new endpoints - for endpoint in ¤t_contract_abi.endpoints { - if !prev_abi.endpoints.contains(endpoint) { - diff_abi.endpoints.push(endpoint.clone()); - } - } + // changed and new upgrade constructors + for upgrade_constructor in ¤t_contract_abi.upgrade_constructor { + if !prev_abi.upgrade_constructor.contains(upgrade_constructor) { + diff_abi + .upgrade_constructor + .push(upgrade_constructor.clone()); + } + } - // deleted endpoints - // bug here when deleting and diff with no overwrite - for endpoint in &prev_abi.endpoints { - if !current_contract_abi.endpoints.contains(endpoint) { - diff_abi.endpoints.retain(|e| e.name != endpoint.name); - } - } + // changed and new endpoints + for endpoint in ¤t_contract_abi.endpoints { + if !prev_abi.endpoints.contains(endpoint) { + diff_abi.endpoints.push(endpoint.clone()); + } + } - println!("diff_abi {diff_abi:?}"); - return diff_abi; - } else { - println!("here") + // deleted endpoints + for endpoint in &prev_abi.endpoints { + if !current_contract_abi.endpoints.contains(endpoint) { + diff_abi.endpoints.retain(|e| e.name != endpoint.name); } } + + return diff_abi; } current_contract_abi.clone() } -pub(crate) fn create_prev_abi_file(snippets_dir: &String, contract_abi: &ContractAbi) { +pub(crate) fn create_prev_abi_file(snippets_dir: &Path, contract_abi: &ContractAbi) { let abi_json = ContractAbiJson::from(contract_abi); let abi_string = serialize_abi_to_json(&abi_json); + let abi_file_path = snippets_dir.join(PREV_ABI_NAME); - let abi_file_path = format!("{snippets_dir}/{PREV_ABI_NAME}"); let mut abi_file = File::create(abi_file_path).unwrap(); write!(abi_file, "{abi_string}").unwrap(); } -pub(crate) fn add_new_endpoints_to_file(snippets_dir: &String, diff_abi: &ShortContractAbi) { - let interact_lib_path = format!("{snippets_dir}/src/{LIB_SOURCE_FILE_NAME}"); +pub(crate) fn add_new_endpoints_to_file(snippets_dir: &Path, diff_abi: &ShortContractAbi) { + let interact_lib_path = snippets_dir.join("src").join(LIB_SOURCE_FILE_NAME); let file_content = std::fs::read_to_string(&interact_lib_path).unwrap(); let mut updated_content = file_content.clone(); @@ -230,7 +228,6 @@ pub(crate) fn add_new_endpoints_to_file(snippets_dir: &String, diff_abi: &ShortC std::fs::write(interact_lib_path, updated_content).unwrap(); } -// this may be buggy fn insert_or_replace_function( file_content: &str, endpoint_abi: &ShortEndpointAbi, 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 fdc90d8a65..3f91aeade2 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 @@ -35,11 +35,8 @@ impl MetaConfig { .name .replace("-", "_"); let original_contract_abi = ShortContractAbi::from(self.original_contract_abi.clone()); - let diff_abi = check_abi_differences( - &original_contract_abi, - &self.snippets_dir.to_string_lossy().to_string(), - args.overwrite, - ); + let diff_abi = + check_abi_differences(&original_contract_abi, &self.snippets_dir, args.overwrite); if diff_abi == original_contract_abi { let mut file = create_snippets_crate_and_get_lib_file( &self.snippets_dir, @@ -57,14 +54,11 @@ impl MetaConfig { crate_name, ); } else { - add_new_endpoints_to_file(&self.snippets_dir.to_string_lossy().to_string(), &diff_abi); + add_new_endpoints_to_file(&self.snippets_dir, &diff_abi); } // create prev-abi.json file - create_prev_abi_file( - &self.snippets_dir.to_string_lossy().to_string(), - &self.original_contract_abi, - ); + create_prev_abi_file(&self.snippets_dir, &self.original_contract_abi); } } 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 a7086f47b9..b2a9d46a21 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 @@ -1,12 +1,6 @@ use std::{fs::File, io::Write, path::Path}; -// use multiversx_sc::abi::{EndpointAbi, EndpointMutabilityAbi, InputAbi}; - -use super::{ - snippet_abi_check::{write_endpoint_impl_to_string, ShortContractAbi}, - // snippet_gen_common::write_newline, - // snippet_type_map::map_abi_type_to_rust_type, -}; +use super::snippet_abi_check::{write_endpoint_impl_to_string, ShortContractAbi}; pub(crate) const DEFAULT_GAS: &str = "30_000_000u64"; @@ -67,216 +61,6 @@ pub(crate) fn write_interact_struct_impl( write!(file, "{}", buffer).unwrap(); - // write_deploy_method_impl(file, &abi.constructor[0], &abi.name); - - // for upgrade_abi in &abi.upgrade_constructor { - // write_upgrade_endpoint_impl(file, upgrade_abi, &abi.name); - // } - - // for endpoint_abi in &abi.endpoints { - // write_endpoint_impl(file, endpoint_abi, &abi.name); - // } - // close impl block brackets writeln!(file, "}}").unwrap(); } - -// pub(crate) fn write_deploy_method_impl(file: &mut File, init_abi: &EndpointAbi, name: &String) { -// write_method_declaration(file, "deploy"); -// write_endpoint_args_declaration(file, &init_abi.inputs); -// let proxy_name = format!("{}Proxy", name); - -// writeln!( -// file, -// r#" let new_address = self -// .interactor -// .tx() -// .from(&self.wallet_address) -// .gas({DEFAULT_GAS}) -// .typed(proxy::{}) -// .init({}) -// .code(&self.contract_code) -// .returns(ReturnsNewAddress) -// .run() -// .await; -// let new_address_bech32 = bech32::encode(&new_address); -// self.state -// .set_address(Bech32Address::from_bech32_string(new_address_bech32.clone())); - -// println!("new address: {{new_address_bech32}}");"#, -// proxy_name, -// endpoint_args_when_called(init_abi.inputs.as_slice()), -// ) -// .unwrap(); - -// // close method block brackets -// writeln!(file, " }}").unwrap(); -// write_newline(file); -// } - -// pub(crate) fn write_upgrade_endpoint_impl( -// file: &mut File, -// upgrade_abi: &EndpointAbi, -// name: &String, -// ) { -// write_method_declaration(file, "upgrade"); -// write_endpoint_args_declaration(file, &upgrade_abi.inputs); -// let proxy_name = format!("{}Proxy", name); - -// writeln!( -// file, -// r#" let response = self -// .interactor -// .tx() -// .to(self.state.current_address()) -// .from(&self.wallet_address) -// .gas({DEFAULT_GAS}) -// .typed(proxy::{}) -// .upgrade({}) -// .code(&self.contract_code) -// .code_metadata(CodeMetadata::UPGRADEABLE) -// .returns(ReturnsResultUnmanaged) -// .run() -// .await; - -// println!("Result: {{response:?}}");"#, -// proxy_name, -// endpoint_args_when_called(upgrade_abi.inputs.as_slice()), -// ) -// .unwrap(); - -// // close method block brackets -// writeln!(file, " }}").unwrap(); -// write_newline(file); -// } - -// pub(crate) fn write_endpoint_impl(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { -// write_method_declaration(file, &endpoint_abi.rust_method_name); -// write_payments_declaration(file, &endpoint_abi.payable_in_tokens); -// write_endpoint_args_declaration(file, &endpoint_abi.inputs); -// if matches!(endpoint_abi.mutability, EndpointMutabilityAbi::Readonly) { -// write_contract_query(file, endpoint_abi, name); -// } else { -// write_contract_call(file, endpoint_abi, name); -// } - -// // close method block brackets -// writeln!(file, " }}").unwrap(); -// write_newline(file); -// } - -// pub(crate) fn write_method_declaration(file: &mut File, endpoint_name: &str) { -// writeln!(file, " pub async fn {endpoint_name}(&mut self) {{").unwrap(); -// } - -// pub(crate) fn write_payments_declaration(file: &mut File, accepted_tokens: &[String]) { -// if accepted_tokens.is_empty() { -// return; -// } - -// // only handle EGLD and "any" case, as they're the most common -// let biguint_default = map_abi_type_to_rust_type("BigUint".to_string()); -// let first_accepted = &accepted_tokens[0]; -// if first_accepted == "EGLD" { -// writeln!( -// file, -// " let egld_amount = {};", -// biguint_default.get_default_value_expr() -// ) -// .unwrap(); -// } else { -// writeln!( -// file, -// " let token_id = String::new(); -// let token_nonce = 0u64; -// let token_amount = {};", -// biguint_default.get_default_value_expr() -// ) -// .unwrap(); -// } - -// write_newline(file); -// } - -// fn write_endpoint_args_declaration(file: &mut File, inputs: &[InputAbi]) { -// if inputs.is_empty() { -// return; -// } - -// for input in inputs { -// let rust_type = map_abi_type_to_rust_type(input.type_names.abi.clone()); -// writeln!( -// file, -// " let {} = {};", -// input.arg_name, -// rust_type.get_default_value_expr() -// ) -// .unwrap(); -// } - -// write_newline(file); -// } - -// pub(crate) fn endpoint_args_when_called(inputs: &[InputAbi]) -> String { -// let mut result = String::new(); -// for input in inputs { -// if !result.is_empty() { -// result.push_str(", "); -// } -// result.push_str(&input.arg_name); -// } -// result -// } - -// fn write_contract_call(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { -// let payment_snippet = if endpoint_abi.payable_in_tokens.is_empty() { -// "" -// } else if endpoint_abi.payable_in_tokens[0] == "EGLD" { -// "\n .egld(egld_amount)" -// } else { -// "\n .payment((TokenIdentifier::from(token_id.as_str()), token_nonce, token_amount))" -// }; - -// writeln!( -// file, -// r#" let response = self -// .interactor -// .tx() -// .from(&self.wallet_address) -// .to(self.state.current_address()) -// .gas({DEFAULT_GAS}) -// .typed(proxy::{}Proxy) -// .{}({}){} -// .returns(ReturnsResultUnmanaged) -// .run() -// .await; - -// println!("Result: {{response:?}}");"#, -// name, -// endpoint_abi.rust_method_name, -// endpoint_args_when_called(endpoint_abi.inputs.as_slice()), -// payment_snippet, -// ) -// .unwrap(); -// } - -// fn write_contract_query(file: &mut File, endpoint_abi: &EndpointAbi, name: &String) { -// writeln!( -// file, -// r#" let result_value = self -// .interactor -// .query() -// .to(self.state.current_address()) -// .typed(proxy::{}Proxy) -// .{}({}) -// .returns(ReturnsResultUnmanaged) -// .run() -// .await; - -// println!("Result: {{result_value:?}}");"#, -// name, -// endpoint_abi.rust_method_name, -// endpoint_args_when_called(endpoint_abi.inputs.as_slice()), -// ) -// .unwrap(); -// } From 735552731acfcadd0d4a19a86d42314ef3e119f5 Mon Sep 17 00:00:00 2001 From: Mihai Calin Luca Date: Wed, 2 Apr 2025 17:47:01 +0200 Subject: [PATCH 4/5] partialEq problems solved, remove function still not working --- .../generate_snippets/snippet_abi_check.rs | 236 +++++++++++++----- 1 file changed, 171 insertions(+), 65 deletions(-) diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs index 38b54cbc54..f0bdb92071 100644 --- a/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_abi_check.rs @@ -29,6 +29,8 @@ pub(crate) struct ShortContractAbi { pub upgrade_constructor: Vec, #[serde(default)] pub endpoints: Vec, + #[serde(skip_deserializing)] + pub deleted_endpoints: Vec, } #[derive(PartialEq, Deserialize, Clone, Debug)] @@ -139,6 +141,7 @@ impl From for ShortContractAbi { .into_iter() .map(ShortEndpointAbi::from) .collect(), + deleted_endpoints: vec![], } } } @@ -150,49 +153,100 @@ pub(crate) fn check_abi_differences( ) -> ShortContractAbi { if !overwrite { let prev_abi_path = snippets_dir.join(PREV_ABI_NAME); - let prev_abi_content = std::fs::read_to_string(&prev_abi_path) - .unwrap_or_else(|_| panic!("Failed to read file at path: {:?}", prev_abi_path)); - let prev_abi = serde_json::from_str::(&prev_abi_content) - .unwrap_or_else(|_| panic!("Failed to deserialize prev-abi.json content")); - - let mut diff_abi = ShortContractAbi { - name: current_contract_abi.name.clone(), - constructor: vec![], - upgrade_constructor: vec![], - endpoints: vec![], - }; - - // changed and new constructors - for constructor in ¤t_contract_abi.constructor { - if !prev_abi.constructor.contains(constructor) { - diff_abi.constructor.push(constructor.clone()); - } - } - // changed and new upgrade constructors - for upgrade_constructor in ¤t_contract_abi.upgrade_constructor { - if !prev_abi.upgrade_constructor.contains(upgrade_constructor) { - diff_abi - .upgrade_constructor - .push(upgrade_constructor.clone()); - } - } + if let Ok(prev_abi_content) = std::fs::read_to_string(&prev_abi_path) { + if let Ok(mut prev_abi) = serde_json::from_str::(&prev_abi_content) { + if prev_abi.constructor[0].name.is_empty() { + prev_abi.constructor[0].name = "init".to_string(); + prev_abi.constructor[0].mutability = "mutable".to_string(); + prev_abi.constructor[0].rust_method_name = "init".to_string(); + } + + if prev_abi.upgrade_constructor[0].name.is_empty() { + prev_abi.upgrade_constructor[0].name = "upgrade".to_string(); + prev_abi.upgrade_constructor[0].mutability = "mutable".to_string(); + prev_abi.upgrade_constructor[0].rust_method_name = "upgrade".to_string(); + } + + println!("prev ABI constructor {:?}", prev_abi.constructor); + println!( + "prev ABI upgrade constructor {:?}", + prev_abi.upgrade_constructor + ); + println!("prev ABI endpdoints {:?}", prev_abi.endpoints); + println!("**********"); + println!( + "CURRENT ABI constructor {:?}", + current_contract_abi.constructor + ); + println!( + "CURRENT ABI upgrade constructor {:?}", + current_contract_abi.upgrade_constructor + ); + println!("CURRENT ABI endpoints {:?}", current_contract_abi.endpoints); + + let mut diff_abi = ShortContractAbi { + name: current_contract_abi.name.clone(), + constructor: vec![], + upgrade_constructor: vec![], + endpoints: vec![], + deleted_endpoints: vec![], + }; + + // changed and new constructors + for constructor in ¤t_contract_abi.constructor { + if !prev_abi + .constructor + .iter() + .any(|e| e.name == constructor.name) + { + diff_abi.constructor.push(constructor.clone()); + } + } + + // changed and new upgrade constructors + // PartialEq doesn't work + for upgrade_constructor in ¤t_contract_abi.upgrade_constructor { + if !prev_abi + .upgrade_constructor + .iter() + .any(|e| e.name == upgrade_constructor.name) + { + diff_abi + .upgrade_constructor + .push(upgrade_constructor.clone()); + } + } - // changed and new endpoints - for endpoint in ¤t_contract_abi.endpoints { - if !prev_abi.endpoints.contains(endpoint) { - diff_abi.endpoints.push(endpoint.clone()); - } - } + // changed and new endpoints + // RUST_METHOD_NAME not inherited from EndpointAbi + for endpoint in ¤t_contract_abi.endpoints { + if !prev_abi.endpoints.iter().any(|e| e.name == endpoint.name) { + diff_abi.endpoints.push(endpoint.clone()); + } + } + + // deleted endpoints + for endpoint in &prev_abi.endpoints { + if !current_contract_abi + .endpoints + .iter() + .any(|e| e.name == endpoint.name) + { + diff_abi.deleted_endpoints.push(endpoint.clone()); + } + } - // deleted endpoints - for endpoint in &prev_abi.endpoints { - if !current_contract_abi.endpoints.contains(endpoint) { - diff_abi.endpoints.retain(|e| e.name != endpoint.name); + // deleted endpoints arrive in struct without a rust name + println!("diff_abi endpoints {:?}", diff_abi.endpoints); + println!( + "diff_abi deleted endpoints {:?}", + diff_abi.deleted_endpoints + ); + + return diff_abi; } } - - return diff_abi; } current_contract_abi.clone() } @@ -211,62 +265,110 @@ pub(crate) fn add_new_endpoints_to_file(snippets_dir: &Path, diff_abi: &ShortCon let file_content = std::fs::read_to_string(&interact_lib_path).unwrap(); let mut updated_content = file_content.clone(); + for deleted_endpoint in &diff_abi.deleted_endpoints { + remove_function2(snippets_dir, deleted_endpoint); + } + for endpoint_abi in &diff_abi.endpoints { - updated_content = - insert_or_replace_function(&updated_content, endpoint_abi, &diff_abi.name); + updated_content = insert_function(&updated_content, endpoint_abi, &diff_abi.name); } for constructor in &diff_abi.constructor { - updated_content = insert_or_replace_function(&updated_content, constructor, &diff_abi.name); + updated_content = insert_function(&updated_content, constructor, &diff_abi.name); } for upgrade_constructor in &diff_abi.upgrade_constructor { - updated_content = - insert_or_replace_function(&updated_content, upgrade_constructor, &diff_abi.name); + updated_content = insert_function(&updated_content, upgrade_constructor, &diff_abi.name); } std::fs::write(interact_lib_path, updated_content).unwrap(); } -fn insert_or_replace_function( +fn insert_function( file_content: &str, endpoint_abi: &ShortEndpointAbi, contract_name: &String, ) -> String { - let function_signature = format!("pub async fn {}", endpoint_abi.rust_method_name); let mut updated_content = file_content.to_string(); + println!("Inserting endpoint with name {:?}", endpoint_abi.name); + let new_function = { let mut function_buffer = String::new(); write_endpoint_impl_to_string(&mut function_buffer, endpoint_abi, contract_name); function_buffer }; - if let Some(start) = file_content.find(&function_signature) { - // remove existing function + updated_content.push_str(&new_function); + updated_content.push('\n'); + + updated_content +} + +pub(crate) fn remove_function2(snippets_dir: &Path, endpoint: &ShortEndpointAbi) { + let interact_lib_path = snippets_dir.join("src").join(LIB_SOURCE_FILE_NAME); + + let file_content = std::fs::read_to_string(&interact_lib_path).unwrap(); + + // find and remove the function + let updated_content = find_and_remove_function(file_content, &endpoint.name); + + // delete the initial file + std::fs::remove_file(&interact_lib_path).unwrap(); + + // create a new file at the same path with the updated content + std::fs::write(&interact_lib_path, &updated_content).unwrap(); +} + +pub(crate) fn find_and_remove_function(file_content: String, endpoint_name: &str) -> String { + let lines: Vec<&str> = file_content.lines().collect(); + + // find the start of the function + if let Some(start_index) = lines.iter().position(|line| { + !line.starts_with("//") + && !line.starts_with("/*") + && !line.ends_with("*/") + && line.contains(&format!("pub async fn {}", endpoint_name)) + }) { + // find the end of the function by tracking brace balance let mut balance = 0; - let mut end = start; - for (i, c) in file_content[start..].char_indices() { - match c { - '{' => balance += 1, - '}' => { - balance -= 1; - if balance == 0 { - end = start + i + 1; - break; - } - }, - _ => {}, + let mut end_index = start_index; + + for (i, line) in lines.iter().enumerate().skip(start_index) { + // count opening and closing braces + balance += line.chars().filter(|&c| c == '{').count() as i32; + balance -= line.chars().filter(|&c| c == '}').count() as i32; + + if balance == 0 { + end_index = i; + break; } } - updated_content.replace_range(start..end, &new_function); + + // remove the function block including whitespace + let updated_lines: Vec<&str> = lines + .iter() + .enumerate() + .filter(|&(i, _)| i < start_index || i > end_index) + .map(|(_, &line)| line) + .collect(); + + // join the lines back together + let updated_content = updated_lines.join("\n"); + + println!("********"); + + println!("updated content after deletion {updated_content:?}"); + + updated_content } else { - // append new function - updated_content.push_str("\n\n"); - updated_content.push_str(&new_function); - } + println!( + "Cannot delete function {:?} from the file. Not found", + endpoint_name + ); - updated_content + file_content + } } pub(crate) fn write_endpoint_impl_to_string( @@ -274,6 +376,10 @@ pub(crate) fn write_endpoint_impl_to_string( endpoint_abi: &ShortEndpointAbi, name: &String, ) { + println!( + "Writing endpoint impl for function with name {:?}", + endpoint_abi.rust_method_name + ); write_method_declaration_to_string(buffer, &endpoint_abi.rust_method_name); write_payments_declaration_to_string(buffer, &endpoint_abi.payable_in_tokens); write_endpoint_args_declaration_to_string(buffer, &endpoint_abi.inputs); From d338edf5bae19f9d2f348021f42ef3af1a8c7a60 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Tue, 1 Jul 2025 18:57:43 +0300 Subject: [PATCH 5/5] removed serde from base --- Cargo.lock | 1 - framework/base/Cargo.toml | 4 ---- framework/base/src/abi.rs | 3 +-- framework/base/src/abi/endpoint_abi.rs | 11 +++++------ framework/base/src/abi/esdt_attribute_abi.rs | 3 +-- framework/base/src/abi/event_abi.rs | 4 ++-- framework/base/src/abi/type_description.rs | 11 +++++------ framework/base/src/abi/type_description_container.rs | 2 +- 8 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b254db7b1d..9c3fcf4b7e 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -3135,7 +3135,6 @@ dependencies = [ "multiversx-sc-codec", "multiversx-sc-derive", "num-traits", - "serde", "unwrap-infallible", ] diff --git a/framework/base/Cargo.toml b/framework/base/Cargo.toml index bacf4fc013..2e81f12402 100644 --- a/framework/base/Cargo.toml +++ b/framework/base/Cargo.toml @@ -38,10 +38,6 @@ hex-literal = "1.0" bitflags = "2.9" num-traits = { version = "=0.2.19", default-features = false } unwrap-infallible = "0.1.5" -serde = { version = "1.0.217", features = [ - "derive", - "alloc", -], default-features = false } generic-array = "1.2.0" [dependencies.multiversx-sc-derive] diff --git a/framework/base/src/abi.rs b/framework/base/src/abi.rs index 42862f4140..dd128161bc 100644 --- a/framework/base/src/abi.rs +++ b/framework/base/src/abi.rs @@ -19,7 +19,6 @@ pub use contract_abi::*; pub use endpoint_abi::*; pub use esdt_attribute_abi::EsdtAttributeAbi; pub use event_abi::*; -use serde::Deserialize; pub use type_abi::*; pub use type_abi_from::*; pub use type_description::*; @@ -27,7 +26,7 @@ pub use type_description_container::*; pub type TypeName = alloc::string::String; -#[derive(Clone, Default, Debug, PartialEq, Eq, Deserialize)] +#[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct TypeNames { pub abi: alloc::string::String, pub rust: alloc::string::String, diff --git a/framework/base/src/abi/endpoint_abi.rs b/framework/base/src/abi/endpoint_abi.rs index 39021e7001..5b84bd1bc4 100644 --- a/framework/base/src/abi/endpoint_abi.rs +++ b/framework/base/src/abi/endpoint_abi.rs @@ -4,16 +4,15 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use serde::Deserialize; -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct InputAbi { pub arg_name: String, pub type_names: TypeNames, pub multi_arg: bool, } -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct OutputAbi { pub output_name: String, pub type_names: TypeNames, @@ -22,7 +21,7 @@ pub struct OutputAbi { pub type OutputAbis = Vec; -#[derive(Clone, Default, Debug, PartialEq, Deserialize)] +#[derive(Clone, Default, Debug, PartialEq)] pub enum EndpointMutabilityAbi { #[default] Mutable, @@ -30,7 +29,7 @@ pub enum EndpointMutabilityAbi { Pure, } -#[derive(Clone, Default, Debug, PartialEq, Deserialize)] +#[derive(Clone, Default, Debug, PartialEq)] pub enum EndpointTypeAbi { #[default] Init, @@ -39,7 +38,7 @@ pub enum EndpointTypeAbi { PromisesCallback, } -#[derive(Clone, Default, Debug, PartialEq, Deserialize)] +#[derive(Clone, Default, Debug, PartialEq)] pub struct EndpointAbi { pub docs: Vec, pub name: String, diff --git a/framework/base/src/abi/esdt_attribute_abi.rs b/framework/base/src/abi/esdt_attribute_abi.rs index 6e3a26dc06..66c8ecd8da 100644 --- a/framework/base/src/abi/esdt_attribute_abi.rs +++ b/framework/base/src/abi/esdt_attribute_abi.rs @@ -1,9 +1,8 @@ use alloc::string::{String, ToString}; -use serde::Deserialize; use super::{TypeAbi, TypeDescriptionContainerImpl, TypeName}; -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct EsdtAttributeAbi { pub ticker: String, pub ty: TypeName, diff --git a/framework/base/src/abi/event_abi.rs b/framework/base/src/abi/event_abi.rs index f40e895add..273457266b 100644 --- a/framework/base/src/abi/event_abi.rs +++ b/framework/base/src/abi/event_abi.rs @@ -4,14 +4,14 @@ use alloc::{ vec::Vec, }; -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct EventInputAbi { pub arg_name: String, pub type_name: TypeName, pub indexed: bool, } -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct EventAbi { pub docs: Vec, pub identifier: String, diff --git a/framework/base/src/abi/type_description.rs b/framework/base/src/abi/type_description.rs index 3b120c87c9..65d9922506 100644 --- a/framework/base/src/abi/type_description.rs +++ b/framework/base/src/abi/type_description.rs @@ -2,11 +2,10 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use serde::Deserialize; use super::TypeNames; -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct TypeDescription { pub docs: Vec, pub names: TypeNames, @@ -47,7 +46,7 @@ impl TypeDescription { } } -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub enum TypeContents { NotSpecified, Enum(Vec), @@ -61,7 +60,7 @@ impl TypeContents { } } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct EnumVariantDescription { pub docs: Vec, pub name: String, @@ -88,7 +87,7 @@ impl EnumVariantDescription { } } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct StructFieldDescription { pub docs: Vec, pub name: String, @@ -111,7 +110,7 @@ impl StructFieldDescription { /// This makes it easier for humans to read readable in the transaction output. /// /// It cannot have data fields, only simple enums allowed. -#[derive(Clone, Debug, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct ExplicitEnumVariantDescription { pub docs: Vec, pub name: String, diff --git a/framework/base/src/abi/type_description_container.rs b/framework/base/src/abi/type_description_container.rs index fd4690e1da..de482334ec 100644 --- a/framework/base/src/abi/type_description_container.rs +++ b/framework/base/src/abi/type_description_container.rs @@ -17,7 +17,7 @@ pub trait TypeDescriptionContainer { fn insert_all(&mut self, other: &Self); } -#[derive(Clone, Default, Debug, PartialEq, Deserialize)] +#[derive(Clone, Default, Debug, PartialEq)] pub struct TypeDescriptionContainerImpl(pub Vec<(TypeNames, TypeDescription)>); impl TypeDescriptionContainer for TypeDescriptionContainerImpl {