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/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/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 } 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/interop_system.go b/pkg/core/interop_system.go index 17f6b00bd..9786cdaf9 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 @@ -250,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 @@ -272,7 +278,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)) diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 750713ce5..512dc4e50 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -110,15 +110,17 @@ 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}, {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}, + 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}, 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/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/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 +} diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index 2c6259334..7caa9b114 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 @@ -185,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}) 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