@@ -37,7 +37,7 @@ use foundry_common::{SELECTOR_LEN, TransactionMaybeSigned, evm::Breakpoints};
3737use foundry_evm_core:: {
3838 InspectorExt ,
3939 abi:: Vm :: stopExpectSafeMemoryCall,
40- backend:: { DatabaseError , DatabaseExt , RevertDiagnostic } ,
40+ backend:: { DatabaseError , DatabaseExt , ForkRevertDiagnostic } ,
4141 constants:: { CHEATCODE_ADDRESS , HARDHAT_CONSOLE_ADDRESS , MAGIC_ASSUME } ,
4242 evm:: { FoundryEvm , new_evm_with_existing_context} ,
4343} ;
@@ -395,8 +395,8 @@ pub struct Cheatcodes {
395395 /// Assume next call can revert and discard fuzz run if it does.
396396 pub assume_no_revert : Option < AssumeNoRevert > ,
397397
398- /// Additional diagnostic for reverts
399- pub fork_revert_diagnostic : Option < RevertDiagnostic > ,
398+ /// Fork revert diagnostic information
399+ pub fork_revert_diagnostic : Option < ForkRevertDiagnostic > ,
400400
401401 /// Recorded storage reads and writes
402402 pub accesses : RecordAccess ,
@@ -666,6 +666,12 @@ impl Cheatcodes {
666666 call : & mut CallInputs ,
667667 executor : & mut impl CheatcodesExecutor ,
668668 ) -> Option < CallOutcome > {
669+ // Initialize fork revert diagnostic for this call if we're in fork mode
670+ if ecx. journaled_state . db ( ) . is_forked_mode ( ) {
671+ let revert_diag = self . fork_revert_diagnostic . get_or_insert_default ( ) ;
672+ revert_diag. current_call_target = Some ( call. target_address ) ;
673+ }
674+
669675 let gas = Gas :: new ( call. gas_limit ) ;
670676 let curr_depth = ecx. journaled_state . depth ( ) ;
671677
@@ -1036,6 +1042,36 @@ impl Cheatcodes {
10361042 None => false ,
10371043 }
10381044 }
1045+
1046+ /// Checks if we should diagnose a fork revert for the current state
1047+ fn should_diagnose_fork_revert ( & self , interpreter : & Interpreter , ecx : Ecx ) -> Option < Address > {
1048+ // Only proceed if this is a REVERT opcode
1049+ if interpreter. bytecode . opcode ( ) != op:: REVERT {
1050+ return None ;
1051+ }
1052+
1053+ // Only handle reverts with empty data (size = 0)
1054+ // Reverts with data already have meaningful error messages
1055+ let size = interpreter. stack . peek ( 1 ) . ok ( ) ?;
1056+ if !size. is_zero ( ) {
1057+ return None ;
1058+ }
1059+
1060+ // Check if fork diagnostic tracking is enabled
1061+ // This is only set when we're in fork mode
1062+ let fork_diagnostic = self . fork_revert_diagnostic . as_ref ( ) ?;
1063+ let call_target = fork_diagnostic. current_call_target ?;
1064+
1065+ // Only diagnose if the call target is different from the test contract
1066+ // We don't want to diagnose reverts within the test contract itself
1067+ if let TxKind :: Call ( test_contract) = ecx. tx . kind
1068+ && call_target != test_contract
1069+ {
1070+ return Some ( call_target) ;
1071+ }
1072+
1073+ None
1074+ }
10391075}
10401076
10411077impl Inspector < EthEvmContext < & mut dyn DatabaseExt > > for Cheatcodes {
@@ -1063,6 +1099,20 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
10631099
10641100 #[ inline]
10651101 fn step ( & mut self , interpreter : & mut Interpreter , ecx : Ecx ) {
1102+ // Check for REVERT opcode to handle fork diagnostics before RevertDiagnostic
1103+ if let Some ( call_target) = self . should_diagnose_fork_revert ( interpreter, ecx) {
1104+ let journaled_state = ecx. journaled_state . clone ( ) ;
1105+ if let Some ( diagnostic) =
1106+ ecx. journaled_state . db ( ) . diagnose_revert ( call_target, & journaled_state)
1107+ {
1108+ // Store the diagnostic
1109+ if let Some ( ref mut fork_diagnostic) = self . fork_revert_diagnostic {
1110+ fork_diagnostic. diagnostic = Some ( diagnostic) ;
1111+ }
1112+ return ;
1113+ }
1114+ }
1115+
10661116 self . pc = interpreter. bytecode . pc ( ) ;
10671117
10681118 // `pauseGasMetering`: pause / resume interpreter gas.
@@ -1140,6 +1190,16 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
11401190 }
11411191
11421192 fn call_end ( & mut self , ecx : Ecx , call : & CallInputs , outcome : & mut CallOutcome ) {
1193+ // Apply fork diagnostic if we have one
1194+ if let Some ( mut fork_diagnostic) = self . fork_revert_diagnostic . take ( ) {
1195+ if outcome. result . is_revert ( ) {
1196+ if let Some ( diagnostic) = fork_diagnostic. diagnostic . take ( ) {
1197+ outcome. result . output = Error :: encode ( diagnostic. to_error_msg ( & self . labels ) ) ;
1198+ return ;
1199+ }
1200+ }
1201+ }
1202+
11431203 let cheatcode_call = call. target_address == CHEATCODE_ADDRESS
11441204 || call. target_address == HARDHAT_CONSOLE_ADDRESS ;
11451205
@@ -1411,33 +1471,7 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
14111471 self . expected_emits . clear ( )
14121472 }
14131473
1414- // this will ensure we don't have false positives when trying to diagnose reverts in fork
1415- // mode
1416- let diag = self . fork_revert_diagnostic . take ( ) ;
1417-
1418- // if there's a revert and a previous call was diagnosed as fork related revert then we can
1419- // return a better error here
1420- if outcome. result . is_revert ( )
1421- && let Some ( err) = diag
1422- {
1423- outcome. result . output = Error :: encode ( err. to_error_msg ( & self . labels ) ) ;
1424- return ;
1425- }
1426-
1427- // try to diagnose reverts in multi-fork mode where a call is made to an address that does
1428- // not exist
1429- if let TxKind :: Call ( test_contract) = ecx. tx . kind {
1430- // if a call to a different contract than the original test contract returned with
1431- // `Stop` we check if the contract actually exists on the active fork
1432- if ecx. journaled_state . db ( ) . is_forked_mode ( )
1433- && outcome. result . result == InstructionResult :: Stop
1434- && call. target_address != test_contract
1435- {
1436- let journaled_state = ecx. journaled_state . clone ( ) ;
1437- self . fork_revert_diagnostic =
1438- ecx. journaled_state . db ( ) . diagnose_revert ( call. target_address , & journaled_state) ;
1439- }
1440- }
1474+ // Fork diagnostics are now handled in the step method to run before RevertDiagnostic
14411475
14421476 // If the depth is 0, then this is the root call terminating
14431477 if ecx. journaled_state . depth ( ) == 0 {
0 commit comments