From 036fff5180dfecf2376618d45ef249cbea492ba3 Mon Sep 17 00:00:00 2001 From: Silver Date: Wed, 29 Oct 2025 22:41:45 +0800 Subject: [PATCH 1/4] feat: warn on soldeer.lock revision mismatch during build --- Cargo.lock | 1 + crates/forge/Cargo.toml | 1 + crates/forge/src/cmd/build.rs | 70 ++++++++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27bceb54eb3cd..7bd19f2dd8256 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4144,6 +4144,7 @@ dependencies = [ "tempfile", "thiserror 2.0.17", "tokio", + "toml 0.9.8", "toml_edit 0.23.7", "tower-http", "tracing", diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 2b8143b334cee..eef90720e2d2d 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -79,6 +79,7 @@ solar.workspace = true strum = { workspace = true, features = ["derive"] } thiserror.workspace = true tokio = { workspace = true, features = ["time"] } +toml.workspace = true toml_edit.workspace = true watchexec = "8.0" watchexec-events = "6.0" diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index 54bb43a879436..c8f53a1ac7797 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -22,11 +22,25 @@ use foundry_config::{ }, filter::expand_globs, }; -use serde::Serialize; -use std::path::PathBuf; +use serde::{Deserialize, Serialize}; +use std::{path::PathBuf, process::Command}; foundry_config::merge_impl_figment_convert!(BuildArgs, build); +#[derive(Debug, Deserialize)] +struct SoldeerLockEntry { + name: String, + version: String, + source: String, + #[serde(default, rename = "checksum")] + _checksum: Option, +} + +#[derive(Debug, Deserialize)] +struct SoldeerLock { + dependencies: Vec, +} + /// CLI arguments for `forge build`. /// /// CLI arguments take the highest precedence in the Config/Figment hierarchy. @@ -80,6 +94,8 @@ impl BuildArgs { config = self.load_config()?; } + self.check_soldeer_lock_consistency(&config); + let project = config.project()?; // Collect sources to compile if build subdirectories specified. @@ -207,6 +223,56 @@ impl BuildArgs { Ok([config.src, config.test, config.script, foundry_toml]) }) } + + /// Check soldeer.lock file consistency with actual git revisions + fn check_soldeer_lock_consistency(&self, config: &Config) { + let soldeer_lock_path = config.root.join("soldeer.lock"); + if !soldeer_lock_path.exists() { + return; + } + + let lock_content = match foundry_common::fs::read_to_string(&soldeer_lock_path) { + Ok(content) => content, + Err(_) => return, + }; + + let soldeer_lock: SoldeerLock = match toml::from_str(&lock_content) { + Ok(lock) => lock, + Err(_) => return, + }; + + for dep in &soldeer_lock.dependencies { + if let Some((_, expected_rev)) = dep.source.split_once('#') { + let dep_dir_name = format!("{}-{}", dep.name, dep.version); + let dep_path = config.root.join("dependencies").join(&dep_dir_name); + + if dep_path.exists() { + let actual_rev = Command::new("git") + .args(["rev-parse", "HEAD"]) + .current_dir(&dep_path) + .output(); + + if let Ok(output) = actual_rev + && output.status.success() + { + let actual_rev = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + if !actual_rev.starts_with(expected_rev) + && !expected_rev.starts_with(&actual_rev) + { + sh_warn!( + "Dependency '{}' revision mismatch: \n Expected (from soldeer.lock): {}\n Actual (in {}): {}", + dep.name, + expected_rev, + dep_dir_name, + actual_rev + ).ok(); + } + } + } + } + } + } } // Make this args a `figment::Provider` so that it can be merged into the `Config` From 8eaf6069528b1d7a6413e608f270634ba6320639 Mon Sep 17 00:00:00 2001 From: Silver Date: Fri, 31 Oct 2025 02:10:55 +0800 Subject: [PATCH 2/4] revert & use soldeer_core APIs for lockfile consistency checks --- Cargo.lock | 2 +- crates/forge/Cargo.toml | 2 +- crates/forge/src/cmd/build.rs | 84 +++++++++++++---------------------- 3 files changed, 34 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bd19f2dd8256..6b4b435661b2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4139,12 +4139,12 @@ dependencies = [ "similar-asserts", "solar-compiler", "soldeer-commands", + "soldeer-core", "strum 0.27.2", "svm-rs", "tempfile", "thiserror 2.0.17", "tokio", - "toml 0.9.8", "toml_edit 0.23.7", "tower-http", "tracing", diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index eef90720e2d2d..13861b920bd4d 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -79,7 +79,6 @@ solar.workspace = true strum = { workspace = true, features = ["derive"] } thiserror.workspace = true tokio = { workspace = true, features = ["time"] } -toml.workspace = true toml_edit.workspace = true watchexec = "8.0" watchexec-events = "6.0" @@ -95,6 +94,7 @@ opener = "0.8" # soldeer soldeer-commands.workspace = true +soldeer-core.workspace = true quick-junit = "0.5.1" [dev-dependencies] diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index c8f53a1ac7797..6c8378428e995 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -22,25 +22,11 @@ use foundry_config::{ }, filter::expand_globs, }; -use serde::{Deserialize, Serialize}; -use std::{path::PathBuf, process::Command}; +use serde::Serialize; +use std::path::PathBuf; foundry_config::merge_impl_figment_convert!(BuildArgs, build); -#[derive(Debug, Deserialize)] -struct SoldeerLockEntry { - name: String, - version: String, - source: String, - #[serde(default, rename = "checksum")] - _checksum: Option, -} - -#[derive(Debug, Deserialize)] -struct SoldeerLock { - dependencies: Vec, -} - /// CLI arguments for `forge build`. /// /// CLI arguments take the highest precedence in the Config/Figment hierarchy. @@ -94,7 +80,7 @@ impl BuildArgs { config = self.load_config()?; } - self.check_soldeer_lock_consistency(&config); + self.check_soldeer_lock_consistency(&config).await; let project = config.project()?; @@ -224,53 +210,47 @@ impl BuildArgs { }) } - /// Check soldeer.lock file consistency with actual git revisions - fn check_soldeer_lock_consistency(&self, config: &Config) { + /// Check soldeer.lock file consistency using soldeer_core APIs + async fn check_soldeer_lock_consistency(&self, config: &Config) { let soldeer_lock_path = config.root.join("soldeer.lock"); if !soldeer_lock_path.exists() { return; } - let lock_content = match foundry_common::fs::read_to_string(&soldeer_lock_path) { - Ok(content) => content, - Err(_) => return, - }; - - let soldeer_lock: SoldeerLock = match toml::from_str(&lock_content) { + let lockfile = match soldeer_core::lock::read_lockfile(&soldeer_lock_path) { Ok(lock) => lock, Err(_) => return, }; - for dep in &soldeer_lock.dependencies { - if let Some((_, expected_rev)) = dep.source.split_once('#') { - let dep_dir_name = format!("{}-{}", dep.name, dep.version); - let dep_path = config.root.join("dependencies").join(&dep_dir_name); - - if dep_path.exists() { - let actual_rev = Command::new("git") - .args(["rev-parse", "HEAD"]) - .current_dir(&dep_path) - .output(); - - if let Ok(output) = actual_rev - && output.status.success() - { - let actual_rev = String::from_utf8_lossy(&output.stdout).trim().to_string(); - - if !actual_rev.starts_with(expected_rev) - && !expected_rev.starts_with(&actual_rev) - { - sh_warn!( - "Dependency '{}' revision mismatch: \n Expected (from soldeer.lock): {}\n Actual (in {}): {}", - dep.name, - expected_rev, - dep_dir_name, - actual_rev - ).ok(); - } + let deps_dir = config.root.join("dependencies"); + for entry in &lockfile.entries { + let dep_name = entry.name(); + let dep_path = deps_dir.join(format!("{}-{}", dep_name, entry.version())); + + if !dep_path.exists() { + continue; + } + + // Use soldeer_core's integrity check + match soldeer_core::install::check_dependency_integrity(entry, &deps_dir).await { + Ok(status) => { + use soldeer_core::install::DependencyStatus; + // Check if status indicates a problem + if matches!(status, DependencyStatus::Missing) { + sh_warn!("Dependency '{}' integrity check failed: {:?}", dep_name, status) + .ok(); + continue; } } + Err(e) => { + sh_warn!("Dependency '{}' integrity check error: {}", dep_name, e).ok(); + continue; + } } + + // For git dependencies, check revision from source URL + // LockEntry doesn't expose source directly, so we'll skip git revision check + // The integrity check above should be sufficient } } } From bdb8c6d3985aac417d65060bd530c2c3411e28ea Mon Sep 17 00:00:00 2001 From: Silver Date: Fri, 31 Oct 2025 17:25:11 +0800 Subject: [PATCH 3/4] delete wrong comment & remove unnecessary check &handle FailedIntegrity --- crates/forge/src/cmd/build.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index 6c8378428e995..9b10dd8ccb233 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -225,32 +225,24 @@ impl BuildArgs { let deps_dir = config.root.join("dependencies"); for entry in &lockfile.entries { let dep_name = entry.name(); - let dep_path = deps_dir.join(format!("{}-{}", dep_name, entry.version())); - - if !dep_path.exists() { - continue; - } // Use soldeer_core's integrity check match soldeer_core::install::check_dependency_integrity(entry, &deps_dir).await { Ok(status) => { use soldeer_core::install::DependencyStatus; // Check if status indicates a problem - if matches!(status, DependencyStatus::Missing) { + if matches!( + status, + DependencyStatus::Missing | DependencyStatus::FailedIntegrity + ) { sh_warn!("Dependency '{}' integrity check failed: {:?}", dep_name, status) .ok(); - continue; } } Err(e) => { sh_warn!("Dependency '{}' integrity check error: {}", dep_name, e).ok(); - continue; } } - - // For git dependencies, check revision from source URL - // LockEntry doesn't expose source directly, so we'll skip git revision check - // The integrity check above should be sufficient } } } From a04afc9fbee6afccc84a0dafe276355135cc874f Mon Sep 17 00:00:00 2001 From: Silver Date: Tue, 18 Nov 2025 03:52:31 +0800 Subject: [PATCH 4/4] add check_foundry_lock_consistenc --- crates/forge/src/cmd/build.rs | 60 ++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index 9b10dd8ccb233..957aead5a146c 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -4,7 +4,7 @@ use eyre::{Context, Result}; use forge_lint::{linter::Linter, sol::SolidityLinter}; use foundry_cli::{ opts::BuildOpts, - utils::{LoadConfig, cache_local_signatures}, + utils::{Git, LoadConfig, cache_local_signatures}, }; use foundry_common::{compile::ProjectCompiler, shell}; use foundry_compilers::{ @@ -81,6 +81,7 @@ impl BuildArgs { } self.check_soldeer_lock_consistency(&config).await; + self.check_foundry_lock_consistency(&config); let project = config.project()?; @@ -245,6 +246,63 @@ impl BuildArgs { } } } + + /// Check foundry.lock file consistency with git submodules + fn check_foundry_lock_consistency(&self, config: &Config) { + use crate::lockfile::{DepIdentifier, FOUNDRY_LOCK, Lockfile}; + + let foundry_lock_path = config.root.join(FOUNDRY_LOCK); + if !foundry_lock_path.exists() { + return; + } + + let git = match Git::new(&config.root) { + Ok(git) => git, + Err(_) => return, // Skip if not a git repo + }; + + let mut lockfile = Lockfile::new(&config.root).with_git(&git); + if lockfile.read().is_err() { + return; + } + + let deps = lockfile.dependencies(); + + for (dep_path, dep_identifier) in deps { + let full_path = config.root.join(&dep_path); + + if !full_path.exists() { + sh_warn!("Dependency '{}' not found at expected path", dep_path.display()).ok(); + continue; + } + + let actual_rev = match git.get_rev("HEAD", &full_path) { + Ok(rev) => rev, + Err(_) => { + sh_warn!("Failed to get git revision for dependency '{}'", dep_path.display()) + .ok(); + continue; + } + }; + + // Compare with the expected revision from lockfile + let expected_rev = match dep_identifier { + DepIdentifier::Branch { rev, .. } + | DepIdentifier::Tag { rev, .. } + | DepIdentifier::Rev { rev, .. } => rev.clone(), + }; + + if actual_rev != expected_rev { + sh_warn!( + "Dependency '{}' revision mismatch: expected '{}', found '{}'", + dep_path.display(), + expected_rev, + actual_rev + ) + .ok(); + } + } + } } // Make this args a `figment::Provider` so that it can be merged into the `Config`