diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index e1cf39d59..b3018e51c 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "strings" "github.com/CityOfZion/neo-go/pkg/core/state" "github.com/CityOfZion/neo-go/pkg/core/transaction" @@ -429,7 +430,6 @@ func (ic *interopContext) accountIsStandard(v *vm.VM) error { return nil } -/* // storageFind finds stored key-value pair. func (ic *interopContext) storageFind(v *vm.VM) error { stcInterface := v.Estack().Pop().Value() @@ -446,16 +446,20 @@ func (ic *interopContext) storageFind(v *vm.VM) error { if err != nil { return err } + + filteredMap := make(map[interface{}]vm.StackItem) for k, v := range siMap { if strings.HasPrefix(k, prefix) { - _ = v - panic("TODO") + filteredMap[k] = vm.NewByteArrayItem(v.Value) } } + item := vm.NewMapIterator(filteredMap) + v.Estack().PushVal(item) + return nil } -*/ + // createContractStateFromVM pops all contract state elements from the VM // evaluation stack, does a lot of checks and returns Contract if it // succeeds. diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 0013e9461..9498d414c 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -34,6 +34,72 @@ import ( * TestRuntimeDeserialize */ +func TestStorageFind(t *testing.T) { + v, contractState, context := createVMAndContractState(t) + + skeys := [][]byte{{0x01, 0x02}, {0x02, 0x01}} + items := []*state.StorageItem{ + { + Value: []byte{0x01, 0x02, 0x03, 0x04}, + }, + { + Value: []byte{0x04, 0x03, 0x02, 0x01}, + }, + } + + require.NoError(t, context.dao.PutContractState(contractState)) + + scriptHash := contractState.ScriptHash() + + for i := range skeys { + err := context.dao.PutStorageItem(scriptHash, skeys[i], items[i]) + require.NoError(t, err) + } + + t.Run("normal invocation", func(t *testing.T) { + v.Estack().PushVal([]byte{0x01}) + v.Estack().PushVal(vm.NewInteropItem(&StorageContext{ScriptHash: scriptHash})) + + err := context.storageFind(v) + require.NoError(t, err) + + var iter *vm.InteropItem + require.NotPanics(t, func() { iter = v.Estack().Top().Interop() }) + + require.NoError(t, context.enumeratorNext(v)) + require.True(t, v.Estack().Pop().Bool()) + + v.Estack().PushVal(iter) + require.NoError(t, context.iteratorKey(v)) + require.Equal(t, []byte{0x01, 0x02}, v.Estack().Pop().Bytes()) + + v.Estack().PushVal(iter) + require.NoError(t, context.enumeratorValue(v)) + require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, v.Estack().Pop().Bytes()) + + v.Estack().PushVal(iter) + require.NoError(t, context.enumeratorNext(v)) + require.False(t, v.Estack().Pop().Bool()) + }) + + t.Run("invalid type for StorageContext", func(t *testing.T) { + v.Estack().PushVal([]byte{0x01}) + v.Estack().PushVal(vm.NewInteropItem(nil)) + + require.Error(t, context.storageFind(v)) + }) + + t.Run("invalid script hash", func(t *testing.T) { + invalidHash := scriptHash + invalidHash[0] = ^invalidHash[0] + + v.Estack().PushVal([]byte{0x01}) + v.Estack().PushVal(vm.NewInteropItem(&StorageContext{ScriptHash: invalidHash})) + + require.Error(t, context.storageFind(v)) + }) +} + func TestHeaderGetVersion(t *testing.T) { v, block, context := createVMAndPushBlock(t) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 4f76f1ea6..3e9b32941 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -173,6 +173,7 @@ var neoInterops = []interopedFunction{ {Name: "Neo.Runtime.Notify", Func: (*interopContext).runtimeNotify, Price: 1}, {Name: "Neo.Runtime.Serialize", Func: (*interopContext).runtimeSerialize, Price: 1}, {Name: "Neo.Storage.Delete", Func: (*interopContext).storageDelete, Price: 100}, + {Name: "Neo.Storage.Find", Func: (*interopContext).storageFind, Price: 1}, {Name: "Neo.Storage.Get", Func: (*interopContext).storageGet, Price: 100}, {Name: "Neo.Storage.GetContext", Func: (*interopContext).storageGetContext, Price: 1}, {Name: "Neo.Storage.GetReadOnlyContext", Func: (*interopContext).storageGetReadOnlyContext, Price: 1}, @@ -187,7 +188,6 @@ var neoInterops = []interopedFunction{ {Name: "Neo.Transaction.GetUnspentCoins", Func: (*interopContext).txGetUnspentCoins, Price: 200}, {Name: "Neo.Transaction.GetWitnesses", Func: (*interopContext).txGetWitnesses, Price: 200}, {Name: "Neo.Witness.GetVerificationScript", Func: (*interopContext).witnessGetVerificationScript, Price: 100}, - // {Name: "Neo.Storage.Find", Func: (*interopContext).storageFind, Price: 1}, // Aliases. {Name: "Neo.Iterator.Next", Func: (*interopContext).enumeratorNext, Price: 1}, diff --git a/pkg/core/interops_test.go b/pkg/core/interops_test.go index b5a3e96b1..490a8402b 100644 --- a/pkg/core/interops_test.go +++ b/pkg/core/interops_test.go @@ -64,6 +64,7 @@ func TestUnexpectedNonInterops(t *testing.T) { (*interopContext).outputGetValue, (*interopContext).storageContextAsReadOnly, (*interopContext).storageDelete, + (*interopContext).storageFind, (*interopContext).storageGet, (*interopContext).storagePut, (*interopContext).storagePutEx,