diff --git a/pkg/smartcontract/param_type.go b/pkg/smartcontract/param_type.go index 7623aa5d1..3c2ab38c1 100644 --- a/pkg/smartcontract/param_type.go +++ b/pkg/smartcontract/param_type.go @@ -30,6 +30,7 @@ const ( ArrayType ParamType = 0x10 MapType ParamType = 0x12 InteropInterfaceType ParamType = 0xf0 + AnyType ParamType = 0xfe VoidType ParamType = 0xff ) @@ -60,6 +61,8 @@ func (pt ParamType) String() string { return "InteropInterface" case VoidType: return "Void" + case AnyType: + return "Any" default: return "" } @@ -154,6 +157,8 @@ func ParseParamType(typ string) (ParamType, error) { return InteropInterfaceType, nil case "void": return VoidType, nil + case "any": + return AnyType, nil default: return UnknownType, errors.Errorf("Unknown contract parameter type: %s", typ) } diff --git a/pkg/vm/opcode/opcode.go b/pkg/vm/opcode/opcode.go index f3e09a5a8..1aa39b64d 100644 --- a/pkg/vm/opcode/opcode.go +++ b/pkg/vm/opcode/opcode.go @@ -89,6 +89,7 @@ const ( PUSHDATA2 Opcode = 0x4D PUSHDATA4 Opcode = 0x4E PUSHM1 Opcode = 0x4F + PUSHNULL Opcode = 0x50 PUSH1 Opcode = 0x51 PUSHT Opcode = PUSH1 PUSH2 Opcode = 0x52 @@ -118,6 +119,8 @@ const ( SYSCALL Opcode = 0x68 TAILCALL Opcode = 0x69 + ISNULL Opcode = 0x70 + // Stack DUPFROMALTSTACK Opcode = 0x6A TOALTSTACK Opcode = 0x6B diff --git a/pkg/vm/stack_item.go b/pkg/vm/stack_item.go index 6cfd772f3..3500281bd 100644 --- a/pkg/vm/stack_item.go +++ b/pkg/vm/stack_item.go @@ -185,6 +185,44 @@ func (i *StructItem) Clone() *StructItem { return ret } +// NullItem represents null on the stack. +type NullItem struct{} + +// String implements StackItem interface. +func (i NullItem) String() string { + return "Null" +} + +// Value implements StackItem interface. +func (i NullItem) Value() interface{} { + return nil +} + +// Dup implements StackItem interface. +// There is no need to perform a real copy here, +// as NullItem has no internal state. +func (i NullItem) Dup() StackItem { + return i +} + +// TryBytes implements StackItem interface. +func (i NullItem) TryBytes() ([]byte, error) { + return nil, errors.New("can't convert Null to ByteArray") +} + +// Equals implements StackItem interface. +func (i NullItem) Equals(s StackItem) bool { + _, ok := s.(NullItem) + return ok +} + +// ToContractParameter implements StackItem interface. +func (i NullItem) ToContractParameter(map[StackItem]bool) smartcontract.Parameter { + return smartcontract.Parameter{ + Type: smartcontract.AnyType, + } +} + // BigIntegerItem represents a big integer on the stack. type BigIntegerItem struct { value *big.Int diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index aeb8b0180..788de6c1f 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -560,6 +560,13 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro case opcode.PUSHDATA1, opcode.PUSHDATA2, opcode.PUSHDATA4: v.estack.PushVal(parameter) + case opcode.PUSHNULL: + v.estack.PushVal(NullItem{}) + + case opcode.ISNULL: + res := v.estack.Pop().value.Equals(NullItem{}) + v.estack.PushVal(res) + // Stack operations. case opcode.TOALTSTACK: v.astack.Push(v.estack.Pop()) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index aab31a408..4c55b88fd 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -197,6 +197,31 @@ func TestStackLimitPUSH1Bad(t *testing.T) { checkVMFailed(t, v) } +func TestPUSHNULL(t *testing.T) { + prog := makeProgram(opcode.PUSHNULL, opcode.PUSHNULL, opcode.EQUAL) + v := load(prog) + require.NoError(t, v.Step()) + require.Equal(t, 1, v.estack.Len()) + runVM(t, v) + require.True(t, v.estack.Pop().Bool()) +} + +func TestISNULL(t *testing.T) { + t.Run("Integer", func(t *testing.T) { + prog := makeProgram(opcode.PUSH1, opcode.ISNULL) + v := load(prog) + runVM(t, v) + require.False(t, v.estack.Pop().Bool()) + }) + + t.Run("Null", func(t *testing.T) { + prog := makeProgram(opcode.PUSHNULL, opcode.ISNULL) + v := load(prog) + runVM(t, v) + require.True(t, v.estack.Pop().Bool()) + }) +} + // appendBigStruct returns a program which: // 1. pushes size Structs on stack // 2. packs them into a new struct