diff --git a/src/neo-vm/ExecutionEngine.cs b/src/neo-vm/ExecutionEngine.cs index c0dd1c50..b82fde70 100644 --- a/src/neo-vm/ExecutionEngine.cs +++ b/src/neo-vm/ExecutionEngine.cs @@ -138,6 +138,13 @@ private bool ExecuteInstruction() Push(new BigInteger(instruction.Operand.Span)); break; } + case OpCode.PUSHA: + { + int position = instruction.TokenI32; + if (position < 0 || position > CurrentContext.Script.Length) return false; + Push(new Pointer(position)); + break; + } case OpCode.PUSHNULL: { Push(StackItem.Null); @@ -197,9 +204,20 @@ private bool ExecuteInstruction() return true; } case OpCode.CALL: + case OpCode.CALLA: { + int position; + if (instruction.OpCode == OpCode.CALLA) + { + if (!TryPop(out Pointer x)) return false; + position = x.Position; + } + else + { + position = context.InstructionPointer + instruction.TokenI16; + } ExecutionContext context_call = context.Clone(); - context_call.InstructionPointer = context.InstructionPointer + instruction.TokenI16; + context_call.InstructionPointer = position; if (context_call.InstructionPointer < 0 || context_call.InstructionPointer > context_call.Script.Length) return false; LoadContext(context_call); break; diff --git a/src/neo-vm/Instruction.cs b/src/neo-vm/Instruction.cs index 94cbf7bf..e3075550 100644 --- a/src/neo-vm/Instruction.cs +++ b/src/neo-vm/Instruction.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers.Binary; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; @@ -34,7 +35,16 @@ public short TokenI16 [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return BitConverter.ToInt16(Operand.Span); + return BinaryPrimitives.ReadInt16LittleEndian(Operand.Span); + } + } + + public int TokenI32 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return BinaryPrimitives.ReadInt32LittleEndian(Operand.Span); } } @@ -52,7 +62,7 @@ public uint TokenU32 [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return BitConverter.ToUInt32(Operand.Span); + return BinaryPrimitives.ReadUInt32LittleEndian(Operand.Span); } } diff --git a/src/neo-vm/OpCode.cs b/src/neo-vm/OpCode.cs index 5c852da3..eb113135 100644 --- a/src/neo-vm/OpCode.cs +++ b/src/neo-vm/OpCode.cs @@ -17,6 +17,11 @@ public enum OpCode : byte [OperandSize(Size = 32)] PUSHINT256 = 0x05, /// + /// Convert the next four bytes to an address, and push the address onto the stack. + /// + [OperandSize(Size = 4)] + PUSHA = 0x0A, + /// /// The item null is pushed onto the stack. /// PUSHNULL = 0x0B, @@ -109,6 +114,11 @@ public enum OpCode : byte PUSH16 = 0x20, // Flow control + + /// + /// Pop the address of a function from the stack, and call the function. + /// + CALLA = 0x3A, /// /// Does nothing. /// diff --git a/src/neo-vm/Types/Pointer.cs b/src/neo-vm/Types/Pointer.cs new file mode 100644 index 00000000..14e93909 --- /dev/null +++ b/src/neo-vm/Types/Pointer.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; + +namespace Neo.VM.Types +{ + [DebuggerDisplay("Type={GetType().Name}, Position={Position}")] + public class Pointer : StackItem + { + public int Position { get; } + + public Pointer(int position) + { + this.Position = position; + } + + public override bool Equals(StackItem other) + { + if (other == this) return true; + if (other is Pointer p) return Position == p.Position; + return false; + } + + public override int GetHashCode() + { + return Position.GetHashCode(); + } + + public override bool ToBoolean() + { + return true; + } + } +} diff --git a/tests/neo-vm.Tests/Tests/OpCodes/Control/CALLA.json b/tests/neo-vm.Tests/Tests/OpCodes/Control/CALLA.json new file mode 100644 index 00000000..34d1eac5 --- /dev/null +++ b/tests/neo-vm.Tests/Tests/OpCodes/Control/CALLA.json @@ -0,0 +1,148 @@ +{ + "category": "Control", + "name": "CALLA", + "tests": [ + { + "name": "Wrong type", + "script": "0x123A", + "steps": [ + { + "actions": [ + "StepInto" + ], + "result": { + "state": "Break", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "CALLA", + "evaluationStack": [ + { + "type": "Integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "StepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": "0x0A070000003A661066", + "steps": [ + { + "actions": [ + "StepInto" + ], + "result": { + "state": "Break", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "CALLA", + "evaluationStack": [ + { + "type": "Pointer", + "value": 7 + } + ] + } + ] + } + }, + { + "actions": [ + "StepInto" + ], + "result": { + "state": "Break", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "PUSH0" + }, + { + "instructionPointer": 6, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "StepInto" + ], + "result": { + "state": "Break", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + }, + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "StepInto" + ], + "result": { + "state": "Break", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "StepInto" + ], + "result": { + "state": "Halt", + "resultStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/neo-vm.Tests/Tests/OpCodes/Push/PUSHA.json b/tests/neo-vm.Tests/Tests/OpCodes/Push/PUSHA.json new file mode 100644 index 00000000..699d5d67 --- /dev/null +++ b/tests/neo-vm.Tests/Tests/OpCodes/Push/PUSHA.json @@ -0,0 +1,74 @@ +{ + "category": "Push", + "name": "PUSHA", + "tests": [ + { + "name": "Out of range [-1]", + "script": "0Affffffff", + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "Fault" + } + } + ] + }, + { + "name": "Out of range [>length]", + "script": "0Affffff7f", + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "Fault" + } + } + ] + }, + { + "name": "Real test", + "script": "0A00000000", + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "Halt", + "resultStack": [ + { + "type": "Pointer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test [=length]", + "script": "0A05000000", + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "Halt", + "resultStack": [ + { + "type": "Pointer", + "value": 5 + } + ] + } + } + ] + } + ] +} diff --git a/tests/neo-vm.Tests/Types/VMUTStackItemType.cs b/tests/neo-vm.Tests/Types/VMUTStackItemType.cs index 02640e5d..aef0b0fd 100644 --- a/tests/neo-vm.Tests/Types/VMUTStackItemType.cs +++ b/tests/neo-vm.Tests/Types/VMUTStackItemType.cs @@ -7,6 +7,11 @@ public enum VMUTStackItemType /// Null, + /// + /// An address of function + /// + Pointer, + /// /// Boolean (true,false) /// diff --git a/tests/neo-vm.Tests/VMJsonTestBase.cs b/tests/neo-vm.Tests/VMJsonTestBase.cs index dad4abaa..2552c0b0 100644 --- a/tests/neo-vm.Tests/VMJsonTestBase.cs +++ b/tests/neo-vm.Tests/VMJsonTestBase.cs @@ -127,6 +127,12 @@ private JObject PrepareJsonItem(VMUTStackItem item) ret.Remove("value"); break; } + case VMUTStackItemType.Pointer: + { + ret["type"] = VMUTStackItemType.Pointer.ToString(); + ret["value"] = item.Value.Value(); + break; + } case VMUTStackItemType.String: { // Easy access @@ -194,6 +200,14 @@ private JToken ItemToJson(StackItem item) ["type"] = type, }; } + case Pointer p: + { + return new JObject + { + ["type"] = type, + ["value"] = p.Position + }; + } case VM.Types.Boolean v: value = new JValue(v.ToBoolean()); break; case VM.Types.Integer v: value = new JValue(v.ToBigInteger().ToString()); break; case VM.Types.ByteArray v: value = new JValue(v.ToByteArray().ToArray()); break;