package runtime import ( "errors" "fmt" "math/big" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "go.uber.org/zap" ) type itemable interface { ToStackItem() stackitem.Item } const ( // MaxEventNameLen is the maximum length of a name for event. MaxEventNameLen = 32 // MaxNotificationSize is the maximum length of a runtime log message. MaxNotificationSize = 1024 // SystemRuntimeLogMessage represents log entry message used for output // of the System.Runtime.Log syscall. SystemRuntimeLogMessage = "runtime log" ) // GetExecutingScriptHash returns executing script hash. func GetExecutingScriptHash(ic *interop.Context) error { return ic.VM.PushContextScriptHash(0) } // GetCallingScriptHash returns calling script hash. // While Executing and Entry script hashes are always valid for non-native contracts, // Calling hash is set explicitly when native contracts are used, because when switching from // one native to another, no operations are performed on invocation stack. func GetCallingScriptHash(ic *interop.Context) error { h := ic.VM.GetCallingScriptHash() ic.VM.Estack().PushItem(stackitem.NewByteArray(h.BytesBE())) return nil } // GetEntryScriptHash returns entry script hash. func GetEntryScriptHash(ic *interop.Context) error { return ic.VM.PushContextScriptHash(len(ic.VM.Istack()) - 1) } // GetScriptContainer returns transaction or block that contains the script // being run. func GetScriptContainer(ic *interop.Context) error { c, ok := ic.Container.(itemable) if !ok { return errors.New("unknown script container") } ic.VM.Estack().PushItem(c.ToStackItem()) return nil } // Platform returns the name of the platform. func Platform(ic *interop.Context) error { ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte("NEO"))) return nil } // GetTrigger returns the script trigger. func GetTrigger(ic *interop.Context) error { ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.Trigger)))) return nil } // Notify should pass stack item to the notify plugin to handle it, but // in neo-go the only meaningful thing to do here is to log. func Notify(ic *interop.Context) error { name := ic.VM.Estack().Pop().String() elem := ic.VM.Estack().Pop() args := elem.Array() if len(name) > MaxEventNameLen { return fmt.Errorf("event name must be less than %d", MaxEventNameLen) } curHash := ic.VM.GetCurrentScriptHash() ctr, err := ic.GetContract(curHash) if err != nil { return errors.New("notifications are not allowed in dynamic scripts") } ev := ctr.Manifest.ABI.GetEvent(name) if ev == nil { ic.Log.Info("bad notification", zap.String("contract", curHash.StringLE()), zap.String("event", name), zap.Error(fmt.Errorf("event %s does not exist", name))) } else { err = ev.CheckCompliance(args) if err != nil { ic.Log.Info("bad notification", zap.String("contract", curHash.StringLE()), zap.String("event", name), zap.Error(err)) } } // But it has to be serializable, otherwise we either have some broken // (recursive) structure inside or an interop item that can't be used // outside of the interop subsystem anyway. bytes, err := ic.DAO.GetItemCtx().Serialize(elem.Item(), false) if err != nil { return fmt.Errorf("bad notification: %w", err) } if len(bytes) > MaxNotificationSize { return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize) } ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array)) return nil } // LoadScript takes a script and arguments from the stack and loads it into the VM. func LoadScript(ic *interop.Context) error { script := ic.VM.Estack().Pop().Bytes() fs := callflag.CallFlag(int32(ic.VM.Estack().Pop().BigInt().Int64())) if fs&^callflag.All != 0 { return errors.New("call flags out of range") } args := ic.VM.Estack().Pop().Array() err := vm.IsScriptCorrect(script, nil) if err != nil { return fmt.Errorf("invalid script: %w", err) } fs = ic.VM.Context().GetCallFlags() & callflag.ReadOnly & fs ic.VM.LoadDynamicScript(script, fs) for e, i := ic.VM.Estack(), len(args)-1; i >= 0; i-- { e.PushItem(args[i]) } return nil } // Log logs the message passed. func Log(ic *interop.Context) error { state := ic.VM.Estack().Pop().String() if len(state) > MaxNotificationSize { return fmt.Errorf("message length shouldn't exceed %v", MaxNotificationSize) } var txHash string if ic.Tx != nil { txHash = ic.Tx.Hash().StringLE() } ic.Log.Info(SystemRuntimeLogMessage, zap.String("tx", txHash), zap.String("script", ic.VM.GetCurrentScriptHash().StringLE()), zap.String("msg", state)) return nil } // GetTime returns timestamp of the block being verified, or the latest // one in the blockchain if no block is given to Context. func GetTime(ic *interop.Context) error { ic.VM.Estack().PushItem(stackitem.NewBigInteger(new(big.Int).SetUint64(ic.Block.Timestamp))) return nil } // BurnGas burns GAS to benefit NEO ecosystem. func BurnGas(ic *interop.Context) error { gas := ic.VM.Estack().Pop().BigInt() if !gas.IsInt64() { return errors.New("invalid GAS value") } g := gas.Int64() if g <= 0 { return errors.New("GAS must be positive") } if !ic.VM.AddGas(g) { return errors.New("GAS limit exceeded") } return nil }