diff --git a/src/neo/SmartContract/ApplicationEngine.cs b/src/neo/SmartContract/ApplicationEngine.cs index 500e75a6b5..fda6a3ba84 100644 --- a/src/neo/SmartContract/ApplicationEngine.cs +++ b/src/neo/SmartContract/ApplicationEngine.cs @@ -25,6 +25,7 @@ public partial class ApplicationEngine : ExecutionEngine public IVerifiable ScriptContainer { get; } public StoreView Snapshot { get; } public long GasConsumed { get; private set; } = 0; + public long GasLeft => testMode ? -1 : gas_amount - GasConsumed; public UInt160 CurrentScriptHash => CurrentContext?.GetState().ScriptHash; public UInt160 CallingScriptHash => CurrentContext?.GetState().CallingScriptHash; diff --git a/src/neo/SmartContract/InteropService.Runtime.cs b/src/neo/SmartContract/InteropService.Runtime.cs index b1c0dec22e..2e1eb8083d 100644 --- a/src/neo/SmartContract/InteropService.Runtime.cs +++ b/src/neo/SmartContract/InteropService.Runtime.cs @@ -29,6 +29,7 @@ public static class Runtime public static readonly InteropDescriptor Log = Register("System.Runtime.Log", Runtime_Log, 0_01000000, TriggerType.All, CallFlags.AllowNotify); public static readonly InteropDescriptor Notify = Register("System.Runtime.Notify", Runtime_Notify, 0_01000000, TriggerType.All, CallFlags.AllowNotify); public static readonly InteropDescriptor GetNotifications = Register("System.Runtime.GetNotifications", Runtime_GetNotifications, 0_00010000, TriggerType.All, CallFlags.None); + public static readonly InteropDescriptor GasLeft = Register("System.Runtime.GasLeft", Runtime_GasLeft, 0_00000400, TriggerType.All, CallFlags.None); private static bool CheckItemForNotification(StackItem state) { @@ -167,6 +168,12 @@ private static bool Runtime_CheckWitness(ApplicationEngine engine) return true; } + private static bool Runtime_GasLeft(ApplicationEngine engine) + { + engine.Push(engine.GasLeft); + return true; + } + private static bool Runtime_GetInvocationCounter(ApplicationEngine engine) { if (!engine.InvocationCounter.TryGetValue(engine.CurrentScriptHash, out var counter)) diff --git a/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs b/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs index 8044d8ae95..442450f844 100644 --- a/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/tests/neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -266,6 +266,57 @@ public void System_ExecutionEngine_GetScriptContainer() } } + [TestMethod] + public void System_Runtime_GasLeft() + { + var snapshot = Blockchain.Singleton.GetSnapshot(); + + using (var script = new ScriptBuilder()) + { + script.Emit(OpCode.NOP); + script.EmitSysCall(InteropService.Runtime.GasLeft); + script.Emit(OpCode.NOP); + script.EmitSysCall(InteropService.Runtime.GasLeft); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + script.EmitSysCall(InteropService.Runtime.GasLeft); + + // Execute + + var engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 100_000_000, false); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(engine.Execute(), VMState.HALT); + + // Check the results + + CollectionAssert.AreEqual + ( + engine.ResultStack.Select(u => (int)((VM.Types.Integer)u).GetBigInteger()).ToArray(), + new int[] { 99_999_570, 99_999_140, 99_998_650 } + ); + } + + // Check test mode + + using (var script = new ScriptBuilder()) + { + script.EmitSysCall(InteropService.Runtime.GasLeft); + + // Execute + + var engine = new ApplicationEngine(TriggerType.Application, null, snapshot, 0, true); + engine.LoadScript(script.ToArray()); + + // Check the results + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsInstanceOfType(engine.ResultStack.Peek(), typeof(Integer)); + Assert.AreEqual(-1, engine.ResultStack.Pop().GetBigInteger()); + } + } + [TestMethod] public void System_Runtime_GetInvocationCounter() {