From 459ac348391e1a5b477b13742daae61a741c1597 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 20 Jul 2020 16:30:19 +0300 Subject: [PATCH 1/9] core: adjust System.Enumerator.Create interop Part of #1201. It should be able to create enumerator from primitive byte-array-like stack items too. --- pkg/core/interop/enumerator/interop.go | 2 +- pkg/interop/enumerator/enumerator.go | 10 +++++----- pkg/vm/interop.go | 21 +++++++++++++++++---- pkg/vm/interop_iterators.go | 24 ++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/pkg/core/interop/enumerator/interop.go b/pkg/core/interop/enumerator/interop.go index ecd90efad..6265d74d1 100644 --- a/pkg/core/interop/enumerator/interop.go +++ b/pkg/core/interop/enumerator/interop.go @@ -10,7 +10,7 @@ func Concat(_ *interop.Context, v *vm.VM) error { return vm.EnumeratorConcat(v) } -// Create creates an enumerator from an array-like stack item. +// Create creates an enumerator from an array-like or bytearray-like stack item. func Create(_ *interop.Context, v *vm.VM) error { return vm.EnumeratorCreate(v) } diff --git a/pkg/interop/enumerator/enumerator.go b/pkg/interop/enumerator/enumerator.go index ca52f4dcc..5581538a8 100644 --- a/pkg/interop/enumerator/enumerator.go +++ b/pkg/interop/enumerator/enumerator.go @@ -9,11 +9,11 @@ package enumerator // or structures that have values with no explicit keys. type Enumerator struct{} -// Create creates a new enumerator from the given items (slice or structure). -// New enumerator points at index -1 of its items, so the user of it has to -// advance it first with Next. This function uses `System.Enumerator.Create` -// syscall. -func Create(items []interface{}) Enumerator { +// Create creates a new enumerator from the given items (slice, structure, byte +// array and integer or boolean converted to byte array). New enumerator points +// at index -1 of its items, so the user of it has to advance it first with Next. +// This function uses `System.Enumerator.Create` syscall. +func Create(items interface{}) Enumerator { return Enumerator{} } diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index 2c6259334..78e1db225 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -126,12 +126,25 @@ func init() { // EnumeratorCreate handles syscall System.Enumerator.Create. func EnumeratorCreate(v *VM) error { - data := v.Estack().Pop().Array() - v.Estack().Push(&Element{ - value: stackitem.NewInterop(&arrayWrapper{ + var interop interface{} + switch t := v.Estack().Pop().value.(type) { + case *stackitem.Array, *stackitem.Struct: + interop = &arrayWrapper{ + index: -1, + value: t.Value().([]stackitem.Item), + } + default: + data, err := t.TryBytes() + if err != nil { + return fmt.Errorf("can not create enumerator from type %s: %v", t.Type(), err) + } + interop = &byteArrayWrapper{ index: -1, value: data, - }), + } + } + v.Estack().Push(&Element{ + value: stackitem.NewInterop(interop), }) return nil diff --git a/pkg/vm/interop_iterators.go b/pkg/vm/interop_iterators.go index 473acf65c..b29de47e3 100644 --- a/pkg/vm/interop_iterators.go +++ b/pkg/vm/interop_iterators.go @@ -1,6 +1,8 @@ package vm import ( + "math/big" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) @@ -15,6 +17,11 @@ type ( value []stackitem.Item } + byteArrayWrapper struct { + index int + value []byte + } + concatEnum struct { current enumerator second enumerator @@ -63,6 +70,23 @@ func (a *arrayWrapper) Key() stackitem.Item { return stackitem.Make(a.index) } +func (a *byteArrayWrapper) Next() bool { + if next := a.index + 1; next < len(a.value) { + a.index = next + return true + } + + return false +} + +func (a *byteArrayWrapper) Value() stackitem.Item { + return stackitem.NewBigInteger(big.NewInt(int64(a.value[a.index]))) +} + +func (a *byteArrayWrapper) Key() stackitem.Item { + return stackitem.Make(a.index) +} + func (c *concatEnum) Next() bool { if c.current.Next() { return true From c9ef7425ac7dbe2ceef43ae68062ae19d7744ca6 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 21 Jul 2020 13:14:16 +0300 Subject: [PATCH 2/9] core: adjust System.Iterator.Create interop Closes #1201. It should be able to iterate over primitive byte-array-like types also. --- pkg/interop/iterator/iterator.go | 9 +++++---- pkg/vm/interop.go | 9 ++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/interop/iterator/iterator.go b/pkg/interop/iterator/iterator.go index 9e295f11e..80be62165 100644 --- a/pkg/interop/iterator/iterator.go +++ b/pkg/interop/iterator/iterator.go @@ -11,10 +11,11 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/enumerator" // structure is similar in function to Neo .net framework's Iterator. type Iterator struct{} -// Create creates an iterator from the given items (array, struct or map). A new -// iterator is set to point at element -1, so to access its first element you -// need to call Next first. This function uses `System.Iterator.Create` syscall. -func Create(items []interface{}) Iterator { +// Create creates an iterator from the given items (array, struct, map, byte +// array or integer and boolean converted to byte array). A new iterator is set +// to point at element -1, so to access its first element you need to call Next +// first. This function uses `System.Iterator.Create` syscall. +func Create(items interface{}) Iterator { return Iterator{} } diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index 78e1db225..7caa9b114 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -198,7 +198,14 @@ func IteratorCreate(v *VM) error { case *stackitem.Map: item = NewMapIterator(t) default: - return errors.New("non-iterable type") + data, err := t.TryBytes() + if err != nil { + return fmt.Errorf("non-iterable type %s", t.Type()) + } + item = stackitem.NewInterop(&byteArrayWrapper{ + index: -1, + value: data, + }) } v.Estack().Push(&Element{value: item}) From 8fed38352383d2d317004fca03664354ae229203 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 21 Jul 2020 14:02:47 +0300 Subject: [PATCH 3/9] core: adjust System.Runtime.CheckWitness interop Part of #1055. It should have `AllowStates` flag. Also removed unreachable code: we can't have such situation when script container is not a transaction in the scope of `CheckWitness` method because: 1. Blocks have their own implementation of CheckWitness for internal usage (it's (bc *Blockchain) verifyHeaderWitnesses method). 2. For the outside calls of System.Runtime.CheckWitness interop (e.g. calls from smart-contract) script container is always a transaction. --- pkg/core/interop/runtime/witness.go | 12 +----------- pkg/core/interops.go | 3 ++- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/pkg/core/interop/runtime/witness.go b/pkg/core/interop/runtime/witness.go index c2b2917c5..d95042ef6 100644 --- a/pkg/core/interop/runtime/witness.go +++ b/pkg/core/interop/runtime/witness.go @@ -19,17 +19,7 @@ func CheckHashedWitness(ic *interop.Context, hash util.Uint160) (bool, error) { return checkScope(ic.DAO, tx, ic.ScriptGetter, hash) } - // only for non-Transaction types (Block, etc.) - hashes, err := ic.Chain.GetScriptHashesForVerifying(ic.Tx) - if err != nil { - return false, errors.Wrap(err, "failed to get script hashes") - } - for _, v := range hashes { - if hash.Equals(v) { - return true, nil - } - } - return false, nil + return false, errors.New("script container is not a transaction") } func checkScope(d dao.DAO, tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) { diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 750713ce5..1ca854529 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -110,7 +110,8 @@ var systemInterops = []interop.Function{ {Name: "System.Iterator.Values", Func: iterator.Values, Price: 400}, {Name: "System.Json.Deserialize", Func: json.Deserialize, Price: 500000}, {Name: "System.Json.Serialize", Func: json.Serialize, Price: 100000}, - {Name: "System.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 30000}, + {Name: "System.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 30000, + RequiredFlags: smartcontract.AllowStates}, {Name: "System.Runtime.GasLeft", Func: runtime.GasLeft, Price: 400}, {Name: "System.Runtime.GetCallingScriptHash", Func: engineGetCallingScriptHash, Price: 400}, {Name: "System.Runtime.GetEntryScriptHash", Func: engineGetEntryScriptHash, Price: 400}, From 990ef5525c48a29d1147ccf55505c9a0178ab156 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 21 Jul 2020 14:23:52 +0300 Subject: [PATCH 4/9] core: adjust System.Runtime.GasLeft interop Part of #1055. In test mode it should return -1. --- pkg/core/interop/runtime/util.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/core/interop/runtime/util.go b/pkg/core/interop/runtime/util.go index e1200055c..522940381 100644 --- a/pkg/core/interop/runtime/util.go +++ b/pkg/core/interop/runtime/util.go @@ -11,7 +11,11 @@ import ( // GasLeft returns remaining amount of GAS. func GasLeft(_ *interop.Context, v *vm.VM) error { - v.Estack().PushVal(v.GasLimit - v.GasConsumed()) + if v.GasLimit == -1 { + v.Estack().PushVal(v.GasLimit) + } else { + v.Estack().PushVal(v.GasLimit - v.GasConsumed()) + } return nil } From 88e003d2191ff8a5cef85f599f72651151bbe7f9 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 21 Jul 2020 14:47:44 +0300 Subject: [PATCH 5/9] core: adjust System.Runtime.GetTime interop Part of #1055. It should have AllowStates flag. --- pkg/core/interops.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 1ca854529..7ee684744 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -119,7 +119,7 @@ var systemInterops = []interop.Function{ {Name: "System.Runtime.GetInvocationCounter", Func: runtime.GetInvocationCounter, Price: 400}, {Name: "System.Runtime.GetScriptContainer", Func: engineGetScriptContainer, Price: 250}, {Name: "System.Runtime.GetTime", Func: runtimeGetTime, Price: 250, - AllowedTriggers: trigger.Application}, + AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowStates}, {Name: "System.Runtime.GetTrigger", Func: runtimeGetTrigger, Price: 250}, {Name: "System.Runtime.Log", Func: runtimeLog, Price: 1000000, RequiredFlags: smartcontract.AllowNotify}, {Name: "System.Runtime.Notify", Func: runtimeNotify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify}, From 76acef18adbf5c3a1834f869c72538b8922b8974 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 21 Jul 2020 15:11:17 +0300 Subject: [PATCH 6/9] core: adjust System.Runtime.Platform interop Part of #1055. Added `Platform` method to compiler. --- pkg/compiler/syscall.go | 1 + pkg/interop/runtime/runtime.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 276acb26b..853c634f3 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -68,6 +68,7 @@ var syscalls = map[string]map[string]Syscall{ "CheckWitness": {"System.Runtime.CheckWitness", false}, "Log": {"System.Runtime.Log", false}, "Notify": {"System.Runtime.Notify", false}, + "Platform": {"System.Runtime.Platform", false}, }, "storage": { "ConvertContextToReadOnly": {"System.Storage.AsReadOnly", false}, diff --git a/pkg/interop/runtime/runtime.go b/pkg/interop/runtime/runtime.go index a45088e1b..ecc392d04 100644 --- a/pkg/interop/runtime/runtime.go +++ b/pkg/interop/runtime/runtime.go @@ -80,3 +80,9 @@ func GetNotifications(h []byte) [][]interface{} { func GetInvocationCounter() int { return 0 } + +// Platform returns the platform name, which is set to be `NEO`. This function uses +// `System.Runtime.Platform` syscall. +func Platform() []byte { + return nil +} From 2c41b7b25400745b0f2dbd42fa01fd244bb32f19 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 21 Jul 2020 15:26:12 +0300 Subject: [PATCH 7/9] core: adjust System.Runtime.Log interop Part of #1055 and #1198. It should check the message length and encoding. --- pkg/core/interop_system.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 17f6b00bd..fd0fb96e8 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "math/big" + "unicode/utf8" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" @@ -29,6 +30,8 @@ const ( MaxTraceableBlocks = transaction.MaxValidUntilBlockIncrement // MaxEventNameLen is the maximum length of a name for event. MaxEventNameLen = 32 + // MaxNotificationSize is the maximum length of a runtime log message. + MaxNotificationSize = 1024 ) // StorageContext contains storing id and read/write flag, it's used as @@ -272,7 +275,14 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error { // runtimeLog logs the message passed. func runtimeLog(ic *interop.Context, v *vm.VM) error { - msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes()) + state := v.Estack().Pop().Bytes() + if len(state) > MaxNotificationSize { + return fmt.Errorf("message length shouldn't exceed %v", MaxNotificationSize) + } + if !utf8.Valid(state) { + return errors.New("log message should be UTF8-encoded") + } + msg := fmt.Sprintf("%q", state) ic.Log.Info("runtime log", zap.Stringer("script", v.GetCurrentScriptHash()), zap.String("logs", msg)) From 120eff92f725b1ca45aab5e87006e306d4a13aa4 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 22 Jul 2020 14:58:27 +0300 Subject: [PATCH 8/9] core: adjust System.Runtime.Notify interop Part of #1198. Notification name should be UTF8-encoded. --- pkg/core/interop_system.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index fd0fb96e8..9786cdaf9 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -253,6 +253,9 @@ func runtimeNotify(ic *interop.Context, v *vm.VM) error { if len(name) > MaxEventNameLen { return fmt.Errorf("event name must be less than %d", MaxEventNameLen) } + if !utf8.Valid(name) { + return errors.New("event name should be UTF8-encoded") + } elem := v.Estack().Pop() args := elem.Array() // But it has to be serializable, otherwise we either have some broken From ef8de3b2dcdd79eea858bc83d27a850e863d1c7a Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 21 Jul 2020 20:09:46 +0300 Subject: [PATCH 9/9] core: adjust System.Runtime.GetNotifications interop Part of #1055. Forgot to add this pretty interop to the list of system interops. --- pkg/core/interops.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 7ee684744..512dc4e50 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -117,6 +117,7 @@ var systemInterops = []interop.Function{ {Name: "System.Runtime.GetEntryScriptHash", Func: engineGetEntryScriptHash, Price: 400}, {Name: "System.Runtime.GetExecutingScriptHash", Func: engineGetExecutingScriptHash, Price: 400}, {Name: "System.Runtime.GetInvocationCounter", Func: runtime.GetInvocationCounter, Price: 400}, + {Name: "System.Runtime.GetNotifications", Func: runtime.GetNotifications, Price: 10000}, {Name: "System.Runtime.GetScriptContainer", Func: engineGetScriptContainer, Price: 250}, {Name: "System.Runtime.GetTime", Func: runtimeGetTime, Price: 250, AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowStates},