neo-go/pkg/core/interop/runtime/engine.go
Anna Shaleva 4945145b09 interop: use executing contract state for permissions checks
Do not use the updated contract state from native Management to perform
permissions checks. We need to use the currently executing state
instead got from the currently executing VM context until context is
unloaded.

Close #3471.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2024-06-03 12:32:10 +03:00

195 lines
5.9 KiB
Go

package runtime
import (
"errors"
"fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"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)
}
curr := ic.VM.Context().GetManifest()
if curr == nil {
return errors.New("notifications are not allowed in dynamic scripts")
}
var (
ev = curr.ABI.GetEvent(name)
checkErr error
curHash = ic.VM.GetCurrentScriptHash()
)
if ev == nil {
checkErr = fmt.Errorf("notification %s does not exist", name)
} else {
err := ev.CheckCompliance(args)
if err != nil {
checkErr = fmt.Errorf("notification %s is invalid: %w", name, err)
}
}
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))
}
// 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
}
// 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
}