@@ -23,7 +23,13 @@ use foundry_config::{
2323 filter:: expand_globs,
2424} ;
2525use serde:: { Deserialize , Serialize } ;
26- use std:: { path:: PathBuf , process:: Command } ;
26+ use sha2:: { Digest , Sha256 } ;
27+ use std:: {
28+ fs,
29+ io:: Read ,
30+ path:: { Path , PathBuf } ,
31+ process:: Command ,
32+ } ;
2733
2834foundry_config:: merge_impl_figment_convert!( BuildArgs , build) ;
2935
@@ -32,8 +38,8 @@ struct SoldeerLockEntry {
3238 name : String ,
3339 version : String ,
3440 source : String ,
35- #[ serde( default , rename = "checksum" ) ]
36- _checksum : Option < String > ,
41+ #[ serde( default ) ]
42+ checksum : Option < String > ,
3743}
3844
3945#[ derive( Debug , Deserialize ) ]
@@ -224,7 +230,7 @@ impl BuildArgs {
224230 } )
225231 }
226232
227- /// Check soldeer.lock file consistency with actual git revisions
233+ /// Check soldeer.lock file consistency with checksums and git revisions
228234 fn check_soldeer_lock_consistency ( & self , config : & Config ) {
229235 let soldeer_lock_path = config. root . join ( "soldeer.lock" ) ;
230236 if !soldeer_lock_path. exists ( ) {
@@ -242,37 +248,100 @@ impl BuildArgs {
242248 } ;
243249
244250 for dep in & soldeer_lock. dependencies {
245- if let Some ( ( _, expected_rev) ) = dep. source . split_once ( '#' ) {
246- let dep_dir_name = format ! ( "{}-{}" , dep. name, dep. version) ;
247- let dep_path = config. root . join ( "dependencies" ) . join ( & dep_dir_name) ;
248-
249- if dep_path. exists ( ) {
250- let actual_rev = Command :: new ( "git" )
251- . args ( [ "rev-parse" , "HEAD" ] )
252- . current_dir ( & dep_path)
253- . output ( ) ;
254-
255- if let Ok ( output) = actual_rev
256- && output. status . success ( )
251+ let dep_dir_name = format ! ( "{}-{}" , dep. name, dep. version) ;
252+ let dep_path = config. root . join ( "dependencies" ) . join ( & dep_dir_name) ;
253+
254+ if !dep_path. exists ( ) {
255+ continue ;
256+ }
257+
258+ // Check checksum if available
259+ if let Some ( expected_checksum) = & dep. checksum
260+ && let Ok ( actual_checksum) = self . calculate_dependency_checksum ( & dep_path)
261+ && expected_checksum != & actual_checksum
262+ {
263+ sh_warn ! (
264+ "Dependency '{}' integrity check failed: \n Expected checksum: {}\n Actual checksum: {}" ,
265+ dep. name,
266+ expected_checksum,
267+ actual_checksum
268+ ) . ok ( ) ;
269+ continue ;
270+ }
271+
272+ // For git dependencies, also check revision
273+ if let Some ( ( _, expected_rev) ) = dep. source . split_once ( '#' )
274+ && dep_path. join ( ".git" ) . exists ( )
275+ {
276+ let actual_rev =
277+ Command :: new ( "git" ) . args ( [ "rev-parse" , "HEAD" ] ) . current_dir ( & dep_path) . output ( ) ;
278+
279+ if let Ok ( output) = actual_rev
280+ && output. status . success ( )
281+ {
282+ let actual_rev = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
283+
284+ if !actual_rev. starts_with ( expected_rev)
285+ && !expected_rev. starts_with ( & actual_rev)
257286 {
258- let actual_rev = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
259-
260- if !actual_rev. starts_with ( expected_rev)
261- && !expected_rev. starts_with ( & actual_rev)
262- {
263- sh_warn ! (
264- "Dependency '{}' revision mismatch: \n Expected (from soldeer.lock): {}\n Actual (in {}): {}" ,
265- dep. name,
266- expected_rev,
267- dep_dir_name,
268- actual_rev
269- ) . ok ( ) ;
270- }
287+ sh_warn ! (
288+ "Dependency '{}' revision mismatch: \n Expected (from soldeer.lock): {}\n Actual: {}" ,
289+ dep. name,
290+ expected_rev,
291+ actual_rev
292+ ) . ok ( ) ;
271293 }
272294 }
273295 }
274296 }
275297 }
298+
299+ /// Calculate checksum for a dependency directory
300+ fn calculate_dependency_checksum ( & self , dep_path : & Path ) -> Result < String > {
301+ let mut hasher = Sha256 :: new ( ) ;
302+ let mut files = Vec :: new ( ) ;
303+
304+ // Collect all files recursively, excluding .git directory
305+ collect_files_recursive ( dep_path, & mut files) ?;
306+
307+ // Sort files for consistent hashing
308+ files. sort ( ) ;
309+
310+ for file_path in files {
311+ // Hash the relative path
312+ let relative_path = file_path. strip_prefix ( dep_path) . unwrap_or ( & file_path) ;
313+ hasher. update ( relative_path. to_string_lossy ( ) . as_bytes ( ) ) ;
314+
315+ // Hash the file contents
316+ let mut file = fs:: File :: open ( & file_path) ?;
317+ let mut buffer = Vec :: new ( ) ;
318+ file. read_to_end ( & mut buffer) ?;
319+ hasher. update ( & buffer) ;
320+ }
321+
322+ let result = hasher. finalize ( ) ;
323+ Ok ( format ! ( "{result:x}" ) )
324+ }
325+ }
326+
327+ /// Recursively collect all files in a directory, excluding .git
328+ fn collect_files_recursive ( dir : & Path , files : & mut Vec < PathBuf > ) -> Result < ( ) > {
329+ if dir. file_name ( ) == Some ( std:: ffi:: OsStr :: new ( ".git" ) ) {
330+ return Ok ( ( ) ) ;
331+ }
332+
333+ for entry in fs:: read_dir ( dir) ? {
334+ let entry = entry?;
335+ let path = entry. path ( ) ;
336+
337+ if path. is_dir ( ) {
338+ collect_files_recursive ( & path, files) ?;
339+ } else if path. is_file ( ) {
340+ files. push ( path) ;
341+ }
342+ }
343+
344+ Ok ( ( ) )
276345}
277346
278347// Make this args a `figment::Provider` so that it can be merged into the `Config`
0 commit comments