diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 6292e07de..b713fd712 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -912,12 +912,33 @@ func (v *VM) execute(ctx *Context, op Instruction) { } v.estack.PushVal(sigok) - case HASKEY, KEYS, VALUES: + case KEYS, VALUES: panic("unimplemented") case NEWMAP: v.estack.Push(&Element{value: NewMapItem()}) + case HASKEY: + key := v.estack.Pop() + validateMapKey(key) + + c := v.estack.Pop() + if c == nil { + panic("no value found") + } + switch t := c.value.(type) { + case *ArrayItem, *StructItem: + index := key.BigInt().Int64() + if index < 0 { + panic("negative index") + } + v.estack.PushVal(index < int64(len(c.Array()))) + case *MapItem: + v.estack.PushVal(t.Has(key.value)) + default: + panic("wrong collection type") + } + // Cryptographic operations. case SHA1: b := v.estack.Pop().Bytes() @@ -961,6 +982,16 @@ func makeArrayOfFalses(n int) []StackItem { return items } +func validateMapKey(key *Element) { + if key == nil { + panic("no key found") + } + switch key.value.(type) { + case *ArrayItem, *StructItem, *MapItem: + panic("key can't be a collection") + } +} + func init() { log.SetPrefix("NEO-GO-VM > ") log.SetFlags(0) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 11562c339..79b2694ac 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -673,6 +673,101 @@ func TestSIZEBool(t *testing.T) { assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) } +func TestHASKEYArrayTrue(t *testing.T) { + prog := makeProgram(PUSH5, NEWARRAY, PUSH4, HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(true), vm.estack.Pop().value) +} + +func TestHASKEYArrayFalse(t *testing.T) { + prog := makeProgram(PUSH5, NEWARRAY, PUSH5, HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) +} + +func TestHASKEYStructTrue(t *testing.T) { + prog := makeProgram(PUSH5, NEWSTRUCT, PUSH4, HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(true), vm.estack.Pop().value) +} + +func TestHASKEYStructFalse(t *testing.T) { + prog := makeProgram(PUSH5, NEWSTRUCT, PUSH5, HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) +} + +func TestHASKEYMapTrue(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(6)) + vm.estack.Push(&Element{value: m}) + vm.estack.PushVal(5) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(true), vm.estack.Pop().value) +} + +func TestHASKEYMapFalse(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + m := NewMapItem() + m.Add(makeStackItem(5), makeStackItem(6)) + vm.estack.Push(&Element{value: m}) + vm.estack.PushVal(6) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) +} + +func TestHASKEYNoArguments(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestHASKEY1Argument(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + vm.estack.PushVal(1) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestHASKEYWrongKeyType(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + vm.estack.PushVal([]StackItem{}) + vm.estack.PushVal([]StackItem{}) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestHASKEYWrongCollectionType(t *testing.T) { + prog := makeProgram(HASKEY) + vm := load(prog) + vm.estack.PushVal(1) + vm.estack.PushVal(2) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + func TestSIGNNoArgument(t *testing.T) { prog := makeProgram(SIGN) vm := load(prog)