diff --git a/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs b/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs index fc32744b96..615fa6da1d 100644 --- a/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs +++ b/neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs @@ -1069,5 +1069,134 @@ public void ToJson() jObj["script"].AsString().Should().Be("4220202020202020202020202020202020202020202020202020202020202020"); jObj["sys_fee"].AsString().Should().Be("4200000000"); } + + [TestMethod] + public void Infinite_Loop_Not_Allowed_On_Application() + { + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.Emit(OpCode.JMP, new byte[] { 0, 0, 0, 0 }); + script = sb.ToArray(); + } + + long netfee = 100000000; // network fee + + // Check Application + long appGas = 0; + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, null, null, netfee, false)) + { + engine.LoadScript(script); + Assert.AreEqual(VMState.FAULT, engine.Execute()); + Assert.AreEqual(0, engine.ResultStack.Count); + appGas += engine.GasConsumed; + } + + // many gas are spent before fault + Assert.AreEqual(appGas, 100000040); + } + + [TestMethod] + public void Infinite_Loop_Not_Allowed_On_Verification() + { + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.Emit(OpCode.JMP, new byte[] { 0, 0, 0, 0 }); + script = sb.ToArray(); + } + + long netfee = 100000000; // network fee + + // Check Verification + long verificationGas = 0; + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, null, null, netfee, false)) + { + engine.LoadScript(script); + Assert.AreEqual(VMState.FAULT, engine.Execute()); + Assert.AreEqual(0, engine.ResultStack.Count); + verificationGas += engine.GasConsumed; + } + + // no gas is spent (backwards jump is not allowed) + Assert.AreEqual(verificationGas, 0); + } + + [TestMethod] + public void Recursion_Not_Allowed_On_Application() + { + // example for recursion (no local variables or parameters) + /* + 00 11: PUSH0 #An empty array of bytes is pushed onto the stack + c5 12: NEWARRAY # + 6b 13: TOALTSTACK # Puts the input onto the top of the alt stack. Removes it from the main stack. + 61 14: NOP # Does nothing. + 65 15: CALL fcff # -4 + 61 18: NOP # Does nothing. + 6c 19: FROMALTSTACK # Puts the input onto the top of the main stack. Removes it from the alt stack. + 75 20: DROP # Removes the top stack item. + 66 21: RET # + */ + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.Emit(OpCode.CALL, new byte[] { 0, 0, 0, 0 }); + script = sb.ToArray(); + } + + long netfee = 100000000; // network fee + + // Check Application + long appGas = 0; + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, null, null, netfee, false)) + { + engine.LoadScript(script); + Assert.AreEqual(VMState.FAULT, engine.Execute()); + Assert.AreEqual(0, engine.ResultStack.Count); + appGas += engine.GasConsumed; + } + // many gas are spent before fault + Assert.AreEqual(appGas, 22528000); + } + + [TestMethod] + public void Recursion_Not_Allowed_On_Verification() + { + // example for recursion (no local variables or parameters) + /* + 00 11: PUSH0 #An empty array of bytes is pushed onto the stack + c5 12: NEWARRAY # + 6b 13: TOALTSTACK # Puts the input onto the top of the alt stack. Removes it from the main stack. + 61 14: NOP # Does nothing. + 65 15: CALL fcff # -4 + 61 18: NOP # Does nothing. + 6c 19: FROMALTSTACK # Puts the input onto the top of the main stack. Removes it from the alt stack. + 75 20: DROP # Removes the top stack item. + 66 21: RET # + */ + + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.Emit(OpCode.CALL, new byte[] { 0, 0, 0, 0 }); + script = sb.ToArray(); + } + + long netfee = 100000000; // network fee + + // Check Verification + long verificationGas = 0; + using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, null, null, netfee, false)) + { + engine.LoadScript(script); + Assert.AreEqual(VMState.FAULT, engine.Execute()); + Assert.AreEqual(0, engine.ResultStack.Count); + verificationGas += engine.GasConsumed; + } + // no gas should be spent (recursion is not allowed) + // TODO: fix recursion + Assert.AreEqual(verificationGas, 22528000); + } } } diff --git a/neo/SmartContract/ApplicationEngine.cs b/neo/SmartContract/ApplicationEngine.cs index 2eeeef52e0..7129190d8c 100644 --- a/neo/SmartContract/ApplicationEngine.cs +++ b/neo/SmartContract/ApplicationEngine.cs @@ -80,7 +80,30 @@ protected override bool PreExecuteInstruction() { if (CurrentContext.InstructionPointer >= CurrentContext.Script.Length) return true; - return AddGas(OpCodePrices[CurrentContext.CurrentInstruction.OpCode]); + + Instruction instruction = CurrentContext.CurrentInstruction; + + if (Trigger == TriggerType.Verification) + { + // no backwards jump in verification mode + + switch (instruction.OpCode) + { + case OpCode.JMP: + case OpCode.JMPIF: + case OpCode.JMPIFNOT: + //case OpCode.CALL: + { + if (instruction.TokenI16 <= 0) + { + return false; + } + break; + } + } + } + + return AddGas(OpCodePrices[instruction.OpCode]); } private static Block CreateDummyBlock(Snapshot snapshot)