1
1
//! Implementations of [`Utilities`](spec::Group::Utilities) cheatcodes.
2
2
3
3
use crate :: { Cheatcode , Cheatcodes , CheatcodesExecutor , CheatsCtxt , Result , Vm :: * } ;
4
- use alloy_dyn_abi:: { eip712_parser:: EncodeType , DynSolType , DynSolValue } ;
5
- use alloy_primitives:: { aliases:: B32 , keccak256, map:: HashMap , B64 , U256 } ;
4
+ use alloy_dyn_abi:: { eip712_parser:: EncodeType , DynSolType , DynSolValue , Resolver } ;
5
+ use alloy_primitives:: { aliases:: B32 , keccak256, map:: HashMap , Bytes , B64 , U256 } ;
6
6
use alloy_sol_types:: SolValue ;
7
7
use foundry_common:: { ens:: namehash, fs, TYPE_BINDING_PREFIX } ;
8
8
use foundry_config:: fs_permissions:: FsAccessKind ;
@@ -321,16 +321,7 @@ impl Cheatcode for eip712HashType_0Call {
321
321
fn apply ( & self , state : & mut Cheatcodes ) -> Result {
322
322
let Self { typeNameOrDefinition } = self ;
323
323
324
- let type_def = if typeNameOrDefinition. contains ( '(' ) {
325
- // If the input contains '(', it must be the type definition
326
- EncodeType :: parse ( typeNameOrDefinition) . and_then ( |parsed| parsed. canonicalize ( ) ) ?
327
- } else {
328
- // Otherwise, it must be the type name
329
- let path = state
330
- . config
331
- . ensure_path_allowed ( & state. config . bind_json_path , FsAccessKind :: Read ) ?;
332
- get_type_def_from_bindings ( typeNameOrDefinition, path, & state. config . root ) ?
333
- } ;
324
+ let type_def = get_canonical_type_def ( typeNameOrDefinition, state, None ) ?;
334
325
335
326
Ok ( keccak256 ( type_def. as_bytes ( ) ) . to_vec ( ) )
336
327
}
@@ -347,8 +338,49 @@ impl Cheatcode for eip712HashType_1Call {
347
338
}
348
339
}
349
340
350
- /// Gets the type definition from the bindings in the provided path. Assumes that read validation
351
- /// for the path has already been checked.
341
+ impl Cheatcode for eip712HashStruct_0Call {
342
+ fn apply ( & self , state : & mut Cheatcodes ) -> Result {
343
+ let Self { typeNameOrDefinition, abiEncodedData } = self ;
344
+
345
+ let type_def = get_canonical_type_def ( typeNameOrDefinition, state, None ) ?;
346
+ let primary = & type_def[ ..type_def. find ( '(' ) . unwrap_or ( type_def. len ( ) ) ] ;
347
+
348
+ get_struct_hash ( primary, & type_def, abiEncodedData)
349
+ }
350
+ }
351
+
352
+ impl Cheatcode for eip712HashStruct_1Call {
353
+ fn apply ( & self , state : & mut Cheatcodes ) -> Result {
354
+ let Self { bindingsPath, typeName, abiEncodedData } = self ;
355
+
356
+ let path = state. config . ensure_path_allowed ( bindingsPath, FsAccessKind :: Read ) ?;
357
+ let type_def = get_type_def_from_bindings ( typeName, path, & state. config . root ) ?;
358
+
359
+ get_struct_hash ( typeName, & type_def, abiEncodedData)
360
+ }
361
+ }
362
+
363
+ fn get_canonical_type_def (
364
+ name_or_def : & String ,
365
+ state : & mut Cheatcodes ,
366
+ path : Option < PathBuf > ,
367
+ ) -> Result < String > {
368
+ let type_def = if name_or_def. contains ( '(' ) {
369
+ // If the input contains '(', it must be the type definition
370
+ EncodeType :: parse ( name_or_def) . and_then ( |parsed| parsed. canonicalize ( ) ) ?
371
+ } else {
372
+ // Otherwise, it must be the type name
373
+ let path = path. as_ref ( ) . unwrap_or ( & state. config . bind_json_path ) ;
374
+ let path = state. config . ensure_path_allowed ( path, FsAccessKind :: Read ) ?;
375
+ get_type_def_from_bindings ( name_or_def, path, & state. config . root ) ?
376
+ } ;
377
+
378
+ Ok ( type_def)
379
+ }
380
+
381
+ /// Gets the type definition from the bindings in the provided path.
382
+ ///
383
+ /// Assumes that read validation for the path has already been checked.
352
384
fn get_type_def_from_bindings ( name : & String , path : PathBuf , root : & PathBuf ) -> Result < String > {
353
385
let content = fs:: read_to_string ( & path) ?;
354
386
@@ -380,3 +412,40 @@ fn get_type_def_from_bindings(name: &String, path: PathBuf, root: &PathBuf) -> R
380
412
}
381
413
}
382
414
}
415
+
416
+ fn get_struct_hash ( primary : & str , type_def : & String , abi_encoded_data : & Bytes ) -> Result {
417
+ let mut resolver = Resolver :: default ( ) ;
418
+
419
+ // Populate the resolver by ingesting the canonical type definition, and then get the
420
+ // corresponding `DynSolType` of the primary type.
421
+ resolver
422
+ . ingest_string ( type_def)
423
+ . map_err ( |e| fmt_err ! ( "Resolver failed to ingest type definition: {e}" ) ) ?;
424
+
425
+ let resolved_sol_type = resolver
426
+ . resolve ( primary)
427
+ . map_err ( |e| fmt_err ! ( "Failed to resolve EIP-712 primary type '{primary}': {e}" ) ) ?;
428
+
429
+ // ABI-decode the bytes into `DynSolValue::CustomStruct`.
430
+ let sol_value = resolved_sol_type. abi_decode ( abi_encoded_data. as_ref ( ) ) . map_err ( |e| {
431
+ fmt_err ! ( "Failed to ABI decode using resolved_sol_type directly for '{primary}': {e}." )
432
+ } ) ?;
433
+
434
+ // Use the resolver to properly encode the data.
435
+ let encoded_data: Vec < u8 > = resolver
436
+ . encode_data ( & sol_value)
437
+ . map_err ( |e| fmt_err ! ( "Failed to EIP-712 encode data for struct '{primary}': {e}" ) ) ?
438
+ . ok_or_else ( || fmt_err ! ( "EIP-712 data encoding returned 'None' for struct '{primary}'" ) ) ?;
439
+
440
+ // Compute the type hash of the primary type.
441
+ let type_hash = resolver
442
+ . type_hash ( primary)
443
+ . map_err ( |e| fmt_err ! ( "Failed to compute typeHash for EIP712 type '{primary}': {e}" ) ) ?;
444
+
445
+ // Compute the struct hash of the concatenated type hash and encoded data.
446
+ let mut bytes_to_hash = Vec :: with_capacity ( 32 + encoded_data. len ( ) ) ;
447
+ bytes_to_hash. extend_from_slice ( type_hash. as_slice ( ) ) ;
448
+ bytes_to_hash. extend_from_slice ( & encoded_data) ;
449
+
450
+ Ok ( keccak256 ( & bytes_to_hash) . to_vec ( ) )
451
+ }
0 commit comments