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;