2020-12-02 09:10:43 +00:00
|
|
|
package runtime
|
|
|
|
|
|
|
|
import (
|
2021-04-29 08:33:21 +00:00
|
|
|
"errors"
|
2020-12-02 09:10:43 +00:00
|
|
|
"fmt"
|
2021-08-30 20:43:17 +00:00
|
|
|
"math/big"
|
2020-12-02 09:10:43 +00:00
|
|
|
|
2023-08-10 11:05:32 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
2020-12-02 09:10:43 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
2023-07-08 10:54:04 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
2022-09-29 13:10:47 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
2020-12-02 09:10:43 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
2022-06-08 15:12:41 +00:00
|
|
|
type itemable interface {
|
|
|
|
ToStackItem() stackitem.Item
|
|
|
|
}
|
|
|
|
|
2020-12-02 09:10:43 +00:00
|
|
|
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
|
2022-10-07 12:27:24 +00:00
|
|
|
// SystemRuntimeLogMessage represents log entry message used for output
|
|
|
|
// of the System.Runtime.Log syscall.
|
|
|
|
SystemRuntimeLogMessage = "runtime log"
|
2020-12-02 09:10:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// GetExecutingScriptHash returns executing script hash.
|
|
|
|
func GetExecutingScriptHash(ic *interop.Context) error {
|
|
|
|
return ic.VM.PushContextScriptHash(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCallingScriptHash returns calling script hash.
|
2020-12-10 13:52:16 +00:00
|
|
|
// 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.
|
2020-12-02 09:10:43 +00:00
|
|
|
func GetCallingScriptHash(ic *interop.Context) error {
|
2020-12-10 13:52:16 +00:00
|
|
|
h := ic.VM.GetCallingScriptHash()
|
2021-08-30 20:43:17 +00:00
|
|
|
ic.VM.Estack().PushItem(stackitem.NewByteArray(h.BytesBE()))
|
2020-12-10 13:52:16 +00:00
|
|
|
return nil
|
2020-12-02 09:10:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetEntryScriptHash returns entry script hash.
|
|
|
|
func GetEntryScriptHash(ic *interop.Context) error {
|
2022-11-17 19:06:49 +00:00
|
|
|
return ic.VM.PushContextScriptHash(len(ic.VM.Istack()) - 1)
|
2020-12-02 09:10:43 +00:00
|
|
|
}
|
|
|
|
|
2022-06-08 15:12:41 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-12-02 09:10:43 +00:00
|
|
|
// Platform returns the name of the platform.
|
|
|
|
func Platform(ic *interop.Context) error {
|
2021-08-30 20:43:17 +00:00
|
|
|
ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte("NEO")))
|
2020-12-02 09:10:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTrigger returns the script trigger.
|
|
|
|
func GetTrigger(ic *interop.Context) error {
|
2021-08-30 20:43:17 +00:00
|
|
|
ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.Trigger))))
|
2020-12-02 09:10:43 +00:00
|
|
|
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()
|
2021-03-31 10:54:53 +00:00
|
|
|
elem := ic.VM.Estack().Pop()
|
|
|
|
args := elem.Array()
|
2020-12-02 09:10:43 +00:00
|
|
|
if len(name) > MaxEventNameLen {
|
|
|
|
return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
|
|
|
|
}
|
2024-06-01 10:03:07 +00:00
|
|
|
curr := ic.VM.Context().GetManifest()
|
|
|
|
if curr == nil {
|
2022-07-27 11:49:53 +00:00
|
|
|
return errors.New("notifications are not allowed in dynamic scripts")
|
|
|
|
}
|
2023-08-10 11:05:32 +00:00
|
|
|
var (
|
2024-06-01 10:03:07 +00:00
|
|
|
ev = curr.ABI.GetEvent(name)
|
2023-08-10 11:05:32 +00:00
|
|
|
checkErr error
|
2024-06-01 10:03:07 +00:00
|
|
|
curHash = ic.VM.GetCurrentScriptHash()
|
2023-08-10 11:05:32 +00:00
|
|
|
)
|
2022-09-21 21:01:23 +00:00
|
|
|
if ev == nil {
|
2023-08-10 11:05:32 +00:00
|
|
|
checkErr = fmt.Errorf("notification %s does not exist", name)
|
2022-09-21 21:01:23 +00:00
|
|
|
} else {
|
2024-06-01 10:03:07 +00:00
|
|
|
err := ev.CheckCompliance(args)
|
2022-09-21 21:01:23 +00:00
|
|
|
if err != nil {
|
2023-08-10 11:05:32 +00:00
|
|
|
checkErr = fmt.Errorf("notification %s is invalid: %w", name, err)
|
2022-09-21 21:01:23 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-10 11:05:32 +00:00
|
|
|
if checkErr != nil {
|
|
|
|
if ic.IsHardforkEnabled(config.HFBasilisk) {
|
|
|
|
return checkErr
|
|
|
|
}
|
|
|
|
ic.Log.Info("bad notification", zap.String("contract", curHash.StringLE()), zap.String("event", name), zap.Error(checkErr))
|
|
|
|
}
|
2022-07-27 11:49:53 +00:00
|
|
|
|
2020-12-02 09:10:43 +00:00
|
|
|
// 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.
|
2022-05-31 17:10:20 +00:00
|
|
|
bytes, err := ic.DAO.GetItemCtx().Serialize(elem.Item(), false)
|
2020-12-02 09:10:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("bad notification: %w", err)
|
|
|
|
}
|
|
|
|
if len(bytes) > MaxNotificationSize {
|
|
|
|
return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
|
|
|
|
}
|
2022-09-21 21:01:23 +00:00
|
|
|
ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
|
2020-12-02 09:10:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-09-29 13:10:47 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-12-02 09:10:43 +00:00
|
|
|
// 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)
|
|
|
|
}
|
2021-04-07 12:32:46 +00:00
|
|
|
var txHash string
|
|
|
|
if ic.Tx != nil {
|
|
|
|
txHash = ic.Tx.Hash().StringLE()
|
|
|
|
}
|
2022-10-07 12:27:24 +00:00
|
|
|
ic.Log.Info(SystemRuntimeLogMessage,
|
2021-04-07 12:32:46 +00:00
|
|
|
zap.String("tx", txHash),
|
|
|
|
zap.String("script", ic.VM.GetCurrentScriptHash().StringLE()),
|
|
|
|
zap.String("msg", state))
|
2020-12-02 09:10:43 +00:00
|
|
|
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 {
|
2021-08-30 20:43:17 +00:00
|
|
|
ic.VM.Estack().PushItem(stackitem.NewBigInteger(new(big.Int).SetUint64(ic.Block.Timestamp)))
|
2020-12-02 09:10:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-04-29 08:33:21 +00:00
|
|
|
|
2022-12-07 13:51:03 +00:00
|
|
|
// BurnGas burns GAS to benefit Neo ecosystem.
|
2021-04-29 08:33:21 +00:00
|
|
|
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
|
|
|
|
}
|
2023-07-08 10:54:04 +00:00
|
|
|
|
|
|
|
// CurrentSigners returns signers of the currently loaded transaction or stackitem.Null
|
|
|
|
// if script container is not a transaction.
|
|
|
|
func CurrentSigners(ic *interop.Context) error {
|
|
|
|
tx, ok := ic.Container.(*transaction.Transaction)
|
|
|
|
if ok {
|
|
|
|
ic.VM.Estack().PushItem(transaction.SignersToStackItem(tx.Signers))
|
|
|
|
} else {
|
|
|
|
ic.VM.Estack().PushItem(stackitem.Null{})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|