diff --git a/src/Neo/SmartContract/ApplicationEngine.Helper.cs b/src/Neo/SmartContract/ApplicationEngine.Helper.cs new file mode 100644 index 0000000000..5d2976c288 --- /dev/null +++ b/src/Neo/SmartContract/ApplicationEngine.Helper.cs @@ -0,0 +1,56 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract.Native; +using Neo.VM; +using System; +using System.Linq; +using System.Text; + +namespace Neo.SmartContract +{ + public partial class ApplicationEngine : ExecutionEngine + { + public string GetEngineStackInfoOnFault(bool exceptionStackTrace = true, bool exceptionMessage = true) + { + if (State != VMState.FAULT || FaultException == null) + return ""; + StringBuilder traceback = new(); + if (CallingScriptHash != null) + traceback.AppendLine($"CallingScriptHash={CallingScriptHash}[{NativeContract.ContractManagement.GetContract(SnapshotCache, CallingScriptHash)?.Manifest.Name}]"); + traceback.AppendLine($"CurrentScriptHash={CurrentScriptHash}[{NativeContract.ContractManagement.GetContract(SnapshotCache, CurrentScriptHash)?.Manifest.Name}]"); + traceback.AppendLine($"EntryScriptHash={EntryScriptHash}"); + + foreach (ExecutionContext context in InvocationStack.Reverse()) + { + UInt160 contextScriptHash = context.GetScriptHash(); + string contextContractName = NativeContract.ContractManagement.GetContract(SnapshotCache, contextScriptHash)?.Manifest.Name; + traceback.AppendLine($"\tInstructionPointer={context.InstructionPointer}, OpCode {context.CurrentInstruction?.OpCode}, Script Length={context.Script.Length} {contextScriptHash}[{contextContractName}]"); + } + traceback.Append(GetEngineExceptionInfo(exceptionStackTrace: exceptionStackTrace, exceptionMessage: exceptionMessage)); + + return traceback.ToString(); + } + + public string GetEngineExceptionInfo(bool exceptionStackTrace = true, bool exceptionMessage = true) + { + if (State != VMState.FAULT || FaultException == null) + return ""; + StringBuilder traceback = new(); + Exception baseException = FaultException.GetBaseException(); + if (exceptionStackTrace) + traceback.AppendLine(baseException.StackTrace); + if (exceptionMessage) + traceback.AppendLine(baseException.Message); + return traceback.ToString(); + } + } +} diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index a670e3b4b7..def978596e 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -167,8 +167,15 @@ public void TestSystem_Contract_Call_Permissions() }; var currentScriptHash = engine.EntryScriptHash; + Assert.AreEqual("", engine.GetEngineStackInfoOnFault()); Assert.AreEqual(VMState.FAULT, engine.Execute()); Assert.IsTrue(engine.FaultException.ToString().Contains($"Cannot Call Method disallowed Of Contract {scriptHash.ToString()}")); + string traceback = engine.GetEngineStackInfoOnFault(); + Assert.IsTrue(traceback.Contains($"Cannot Call Method disallowed Of Contract {scriptHash.ToString()}")); + Assert.IsTrue(traceback.Contains("CurrentScriptHash")); + Assert.IsTrue(traceback.Contains("EntryScriptHash")); + Assert.IsTrue(traceback.Contains("InstructionPointer")); + Assert.IsTrue(traceback.Contains("OpCode SYSCALL, Script Length=")); } // Allowed method call.