diff --git a/packages/cw-orch-core/src/contract/paths.rs b/packages/cw-orch-core/src/contract/paths.rs index 4b25f82a..1dea6f07 100644 --- a/packages/cw-orch-core/src/contract/paths.rs +++ b/packages/cw-orch-core/src/contract/paths.rs @@ -155,6 +155,13 @@ mod artifacts_dir { pub fn find_wasm_path(&self, name: &str) -> Result { self.find_wasm_path_with_build_postfix(name, ::None) } + /// Find a WASM file in the artifacts directory that contains the given contracts crates.io label. + /// This function normalizes crates.io labels by stripping the "crates.io:" prefix and converting hyphens to underscores. + /// Example: "crates.io:bs721-account" becomes "bs721_account" + pub fn find_wasm_path_from_crates_label(&self, crates_label: &str) -> Result { + let normalized_name = normalize_contract_name(crates_label); + self.find_wasm_path_with_build_postfix(&normalized_name, ::None) + } /// Find a WASM file in the artifacts directory that contains the given contract name AND build post-fix. /// If a build with the post-fix is not found, the default build will be used. @@ -172,6 +179,10 @@ mod artifacts_dir { let mut default_wasm = None; let mut arm_default_wasm = None; + // Collect all potential matches, prioritizing exact matches + let mut exact_default_wasm = None; + let mut exact_arm_default_wasm = None; + for entry in fs::read_dir(self.path())?.flatten() { let path = entry.path(); // Skip if not a wasm file @@ -180,26 +191,45 @@ mod artifacts_dir { } let file_name = path.file_name().unwrap_or_default().to_string_lossy(); - // Wasm with build postfix, non-ARM + + // Wasm with build postfix, non-ARM (highest priority) if is_artifact_with_build_postfix(&file_name, name, &build_postfix) { - wasm_with_postfix = Some(file_name.into_owned()); - // As it's highest priority we just the loop end here - break; + // Prefer exact matches even within the same priority level + if wasm_with_postfix.is_none() || is_exact_match(&file_name, name) { + wasm_with_postfix = Some(file_name.clone().into_owned()); + if is_exact_match(&file_name, name) { + break; // Found exact match, no need to continue + } + } } - - // Check other valid filenames - if is_arm_artifact_with_build_postfix(&file_name, name, &build_postfix) { - // Wasm with build postfix, ARM - arm_wasm_with_postfix = Some(file_name.into_owned()) - } else if is_default_artifact(&file_name, name) { - // Wasm without build postfix, non-ARM - default_wasm = Some(file_name.into_owned()) - } else if is_default_arm_artifact(&file_name, name) { - // Wasm without build postfix, ARM - arm_default_wasm = Some(file_name.into_owned()) + // Wasm with build postfix, ARM + else if is_arm_artifact_with_build_postfix(&file_name, name, &build_postfix) { + if arm_wasm_with_postfix.is_none() || is_exact_arm_match(&file_name, name) { + arm_wasm_with_postfix = Some(file_name.into_owned()); + } + } + // Wasm without build postfix, non-ARM + else if is_default_artifact(&file_name, name) { + if file_name == format!("{name}.wasm") { + exact_default_wasm = Some(file_name.into_owned()); + } else if default_wasm.is_none() { + default_wasm = Some(file_name.into_owned()); + } + } + // Wasm without build postfix, ARM + else if is_default_arm_artifact(&file_name, name) { + if file_name == format!("{name}{ARM_POSTFIX}.wasm") { + exact_arm_default_wasm = Some(file_name.into_owned()); + } else if arm_default_wasm.is_none() { + arm_default_wasm = Some(file_name.into_owned()); + } } } + // Prefer exact matches within each category + let default_wasm = exact_default_wasm.or(default_wasm); + let arm_default_wasm = exact_arm_default_wasm.or(arm_default_wasm); + let path_str = wasm_with_postfix .or(arm_wasm_with_postfix) .or(default_wasm) @@ -215,7 +245,21 @@ mod artifacts_dir { } fn is_artifact(file_name: &str, contract_name: &str) -> bool { - file_name.contains(contract_name) + // More precise matching to avoid false positives like "someframework_niece.wasm" when looking for "someframework" + // Support patterns like: contract_name.wasm, contract_name-suffix.wasm, contract_name_suffix.wasm + let stem = file_name.strip_suffix(".wasm").unwrap_or(file_name); + + // Exact match + if stem == contract_name { + return true; + } + + // Starts with contract_name followed by delimiter (underscore or hyphen) + if stem.starts_with(&format!("{contract_name}_")) || stem.starts_with(&format!("{contract_name}-")) { + return true; + } + + false } fn is_default_artifact(file_name: &str, contract_name: &str) -> bool { @@ -243,4 +287,22 @@ mod artifacts_dir { is_artifact(file_name, contract_name) && file_name.ends_with(format!("{build_postfix}{ARM_POSTFIX}.wasm").as_str()) } + + fn is_exact_match(file_name: &str, contract_name: &str) -> bool { + let stem = file_name.strip_suffix(".wasm").unwrap_or(file_name); + stem == contract_name + } + + fn is_exact_arm_match(file_name: &str, contract_name: &str) -> bool { + let stem = file_name.strip_suffix(".wasm").unwrap_or(file_name); + stem == format!("{contract_name}{ARM_POSTFIX}") + } + + /// Normalize a contract name from a crates.io label to the format used by the Rust compiler. + /// Strips "crates.io:" prefix if present and converts hyphens to underscores. + /// Example: "crates.io:bs721-account" -> "bs721_account" + fn normalize_contract_name(name: &str) -> String { + let stripped = name.strip_prefix("crates.io:").unwrap_or(name); + stripped.replace('-', "_") + } }