diff --git a/madara/crates/primitives/receipt/src/revert_error.rs b/madara/crates/primitives/receipt/src/revert_error.rs index 6b786379f1..aab96666b1 100644 --- a/madara/crates/primitives/receipt/src/revert_error.rs +++ b/madara/crates/primitives/receipt/src/revert_error.rs @@ -63,8 +63,9 @@ impl RevertErrorExt for RevertError { /// Rules: /// - Always keep VM tracebacks that belong to LibraryCall entries /// - For CallContract entries: -/// - Keep the traceback if the next entry is a LibraryCall or if it's the last entry -/// - Remove the traceback if the next entry is another CallContract +/// - Keep if followed by a LibraryCall or if it's the last CallContract before error +/// - Keep if the next CallContract is the last one before error (preserve call chain context) +/// - Remove if followed by a CallContract that has more CallContracts after it /// - If no owning entry point is found, keep the traceback (safety default) fn should_keep_vm_traceback(error_stack: &ErrorStack, vm_index: usize) -> bool { let owning_entry = find_parent_entry_point(error_stack, vm_index); @@ -76,7 +77,9 @@ fn should_keep_vm_traceback(error_stack: &ErrorStack, vm_index: usize) -> bool { true } else { // For CallContract entries, check what comes next + // Keep if no nested CallContract, OR if the next CallContract is the last one !has_nested_call_contract_after(error_stack, vm_index) + || is_next_call_contract_last(error_stack, vm_index) } } // If we can't find an owning entry, keep the traceback @@ -200,6 +203,38 @@ fn has_nested_call_contract_after(error_stack: &ErrorStack, vm_index: usize) -> false } +/// Checks if the next CallContract after this VM traceback is the last one before the error. +/// Returns true only if: +/// 1. The next EntryPoint is a CallContract +/// 2. There are no more EntryPoints after it (just error messages) +/// +/// This specifically handles the case where CallContract chain ends in error without LibraryCall. +fn is_next_call_contract_last(error_stack: &ErrorStack, vm_index: usize) -> bool { + let mut found_next_entry = false; + + // Scan forward to find EntryPoints + for segment in error_stack.stack.iter().skip(vm_index + 1) { + if let ErrorStackSegment::EntryPoint(entry_point) = segment { + if !found_next_entry { + // This is the next entry point + if entry_point.preamble_type != PreambleType::CallContract { + // Next entry is not a CallContract, so doesn't apply + return false; + } + found_next_entry = true; + } else { + // Found a second entry point after the next CallContract + // If there's another EntryPoint (CallContract or LibraryCall), return false + // We only want to keep both when the chain ends without more EntryPoints + return false; + } + } + } + + // If we found the next CallContract and no more EntryPoints after it, it's the last + found_next_entry +} + #[cfg(test)] mod tests { use super::*; @@ -451,4 +486,73 @@ mod tests { assert_eq!(result, expected); } + + #[test] + fn test_format_for_receipt_filters_redundant_tracebacks_3() { + // Build the ErrorStack structure that represents the unfiltered error + let mut error_stack = ErrorStack { + header: ErrorStackHeader::Execution, + stack: vec![], + }; + + // Entry 0: CallContract with VM traceback (should be kept - last before error) + error_stack.push( + EntryPointErrorFrame { + depth: 0, + preamble_type: PreambleType::CallContract, + storage_address: test_contract_address!("0x01f062c02ee674cc7a88dd94e0b230b76decf76aff55b83ec32a90936e7569ab"), + class_hash: test_class_hash!("0x073414441639dcd11d1846f287650a00c60c416b9d3ba45d31c651672125b2c2"), + selector: Some(EntryPointSelector(test_felt!("0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad"))), + } + .into(), + ); + error_stack.push( + VmExceptionFrame { + pc: Relocatable { segment_index: 0, offset: 35988 }, + error_attr_value: None, + traceback: Some("Cairo traceback (most recent call last):\nUnknown location (pc=0:330)\nUnknown location (pc=0:11695)\n".to_string()), + } + .into(), + ); + + // Entry 1: CallContract with VM traceback (should be kept - last before error) + error_stack.push( + EntryPointErrorFrame { + depth: 1, + preamble_type: PreambleType::CallContract, + storage_address: test_contract_address!("0x02953d14869a4f634e02272ac288713dc514bfd018857569252b74f4a96e91fc"), + class_hash: test_class_hash!("0x05e4b69d808cd273b7d84ea27f1954c1eb8b61211036d293b1a0d5e9f34726e8"), + selector: Some(EntryPointSelector(test_felt!("0x00aceca4cf913a062eea8c1609ce381630d82808d51e757d7b2b68c961933fa8"))), + } + .into(), + ); + error_stack.push( + VmExceptionFrame { + pc: Relocatable { segment_index: 0, offset: 117929 }, + error_attr_value: None, + traceback: Some("Cairo traceback (most recent call last):\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\n".to_string()), + } + .into(), + ); + + // Final error message + error_stack.push( + ErrorStackSegment::StringFrame("Exceeded the maximum number of events, number events: 1001, max number events: 1000.".to_string()), + ); + + let revert_error = RevertError::Execution(error_stack); + + // Verify that the RevertError structure produces the correct input (unfiltered) + let input = "Transaction execution has failed:\n0: Error in the called contract (contract address: 0x01f062c02ee674cc7a88dd94e0b230b76decf76aff55b83ec32a90936e7569ab, class hash: 0x073414441639dcd11d1846f287650a00c60c416b9d3ba45d31c651672125b2c2, selector: 0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad):\nError at pc=0:35988:\nCairo traceback (most recent call last):\nUnknown location (pc=0:330)\nUnknown location (pc=0:11695)\n\n1: Error in the called contract (contract address: 0x02953d14869a4f634e02272ac288713dc514bfd018857569252b74f4a96e91fc, class hash: 0x05e4b69d808cd273b7d84ea27f1954c1eb8b61211036d293b1a0d5e9f34726e8, selector: 0x00aceca4cf913a062eea8c1609ce381630d82808d51e757d7b2b68c961933fa8):\nError at pc=0:117929:\nCairo traceback (most recent call last):\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\n\nExceeded the maximum number of events, number events: 1001, max number events: 1000."; + assert_eq!(revert_error.to_string(), input); + + // Expected output: all tracebacks kept (entry 0 before LibraryCall, entry 1 belongs to LibraryCall, entry 2 before LibraryCall) + let expected = "Transaction execution has failed:\n0: Error in the called contract (contract address: 0x01f062c02ee674cc7a88dd94e0b230b76decf76aff55b83ec32a90936e7569ab, class hash: 0x073414441639dcd11d1846f287650a00c60c416b9d3ba45d31c651672125b2c2, selector: 0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad):\nError at pc=0:35988:\nCairo traceback (most recent call last):\nUnknown location (pc=0:330)\nUnknown location (pc=0:11695)\n\n1: Error in the called contract (contract address: 0x02953d14869a4f634e02272ac288713dc514bfd018857569252b74f4a96e91fc, class hash: 0x05e4b69d808cd273b7d84ea27f1954c1eb8b61211036d293b1a0d5e9f34726e8, selector: 0x00aceca4cf913a062eea8c1609ce381630d82808d51e757d7b2b68c961933fa8):\nError at pc=0:117929:\nCairo traceback (most recent call last):\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\nUnknown location (pc=0:118178)\n\nExceeded the maximum number of events, number events: 1001, max number events: 1000."; + + let result = revert_error.to_string(); + + assert_eq!(result, expected); + } + + }