github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/interop/runtime/engine.go (about)

     1  package runtime
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  
     8  	"github.com/nspcc-dev/neo-go/pkg/config"
     9  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    10  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    11  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    12  	"github.com/nspcc-dev/neo-go/pkg/vm"
    13  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    14  	"go.uber.org/zap"
    15  )
    16  
    17  type itemable interface {
    18  	ToStackItem() stackitem.Item
    19  }
    20  
    21  const (
    22  	// MaxEventNameLen is the maximum length of a name for event.
    23  	MaxEventNameLen = 32
    24  	// MaxNotificationSize is the maximum length of a runtime log message.
    25  	MaxNotificationSize = 1024
    26  	// SystemRuntimeLogMessage represents log entry message used for output
    27  	// of the System.Runtime.Log syscall.
    28  	SystemRuntimeLogMessage = "runtime log"
    29  )
    30  
    31  // GetExecutingScriptHash returns executing script hash.
    32  func GetExecutingScriptHash(ic *interop.Context) error {
    33  	return ic.VM.PushContextScriptHash(0)
    34  }
    35  
    36  // GetCallingScriptHash returns calling script hash.
    37  // While Executing and Entry script hashes are always valid for non-native contracts,
    38  // Calling hash is set explicitly when native contracts are used, because when switching from
    39  // one native to another, no operations are performed on invocation stack.
    40  func GetCallingScriptHash(ic *interop.Context) error {
    41  	h := ic.VM.GetCallingScriptHash()
    42  	ic.VM.Estack().PushItem(stackitem.NewByteArray(h.BytesBE()))
    43  	return nil
    44  }
    45  
    46  // GetEntryScriptHash returns entry script hash.
    47  func GetEntryScriptHash(ic *interop.Context) error {
    48  	return ic.VM.PushContextScriptHash(len(ic.VM.Istack()) - 1)
    49  }
    50  
    51  // GetScriptContainer returns transaction or block that contains the script
    52  // being run.
    53  func GetScriptContainer(ic *interop.Context) error {
    54  	c, ok := ic.Container.(itemable)
    55  	if !ok {
    56  		return errors.New("unknown script container")
    57  	}
    58  	ic.VM.Estack().PushItem(c.ToStackItem())
    59  	return nil
    60  }
    61  
    62  // Platform returns the name of the platform.
    63  func Platform(ic *interop.Context) error {
    64  	ic.VM.Estack().PushItem(stackitem.NewByteArray([]byte("NEO")))
    65  	return nil
    66  }
    67  
    68  // GetTrigger returns the script trigger.
    69  func GetTrigger(ic *interop.Context) error {
    70  	ic.VM.Estack().PushItem(stackitem.NewBigInteger(big.NewInt(int64(ic.Trigger))))
    71  	return nil
    72  }
    73  
    74  // Notify should pass stack item to the notify plugin to handle it, but
    75  // in neo-go the only meaningful thing to do here is to log.
    76  func Notify(ic *interop.Context) error {
    77  	name := ic.VM.Estack().Pop().String()
    78  	elem := ic.VM.Estack().Pop()
    79  	args := elem.Array()
    80  	if len(name) > MaxEventNameLen {
    81  		return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
    82  	}
    83  	curHash := ic.VM.GetCurrentScriptHash()
    84  	ctr, err := ic.GetContract(curHash)
    85  	if err != nil {
    86  		return errors.New("notifications are not allowed in dynamic scripts")
    87  	}
    88  	var (
    89  		ev       = ctr.Manifest.ABI.GetEvent(name)
    90  		checkErr error
    91  	)
    92  	if ev == nil {
    93  		checkErr = fmt.Errorf("notification %s does not exist", name)
    94  	} else {
    95  		err = ev.CheckCompliance(args)
    96  		if err != nil {
    97  			checkErr = fmt.Errorf("notification %s is invalid: %w", name, err)
    98  		}
    99  	}
   100  	if checkErr != nil {
   101  		if ic.IsHardforkEnabled(config.HFBasilisk) {
   102  			return checkErr
   103  		}
   104  		ic.Log.Info("bad notification", zap.String("contract", curHash.StringLE()), zap.String("event", name), zap.Error(checkErr))
   105  	}
   106  
   107  	// But it has to be serializable, otherwise we either have some broken
   108  	// (recursive) structure inside or an interop item that can't be used
   109  	// outside of the interop subsystem anyway.
   110  	bytes, err := ic.DAO.GetItemCtx().Serialize(elem.Item(), false)
   111  	if err != nil {
   112  		return fmt.Errorf("bad notification: %w", err)
   113  	}
   114  	if len(bytes) > MaxNotificationSize {
   115  		return fmt.Errorf("notification size shouldn't exceed %d", MaxNotificationSize)
   116  	}
   117  	ic.AddNotification(curHash, name, stackitem.DeepCopy(stackitem.NewArray(args), true).(*stackitem.Array))
   118  	return nil
   119  }
   120  
   121  // LoadScript takes a script and arguments from the stack and loads it into the VM.
   122  func LoadScript(ic *interop.Context) error {
   123  	script := ic.VM.Estack().Pop().Bytes()
   124  	fs := callflag.CallFlag(int32(ic.VM.Estack().Pop().BigInt().Int64()))
   125  	if fs&^callflag.All != 0 {
   126  		return errors.New("call flags out of range")
   127  	}
   128  	args := ic.VM.Estack().Pop().Array()
   129  	err := vm.IsScriptCorrect(script, nil)
   130  	if err != nil {
   131  		return fmt.Errorf("invalid script: %w", err)
   132  	}
   133  	fs = ic.VM.Context().GetCallFlags() & callflag.ReadOnly & fs
   134  	ic.VM.LoadDynamicScript(script, fs)
   135  
   136  	for e, i := ic.VM.Estack(), len(args)-1; i >= 0; i-- {
   137  		e.PushItem(args[i])
   138  	}
   139  	return nil
   140  }
   141  
   142  // Log logs the message passed.
   143  func Log(ic *interop.Context) error {
   144  	state := ic.VM.Estack().Pop().String()
   145  	if len(state) > MaxNotificationSize {
   146  		return fmt.Errorf("message length shouldn't exceed %v", MaxNotificationSize)
   147  	}
   148  	var txHash string
   149  	if ic.Tx != nil {
   150  		txHash = ic.Tx.Hash().StringLE()
   151  	}
   152  	ic.Log.Info(SystemRuntimeLogMessage,
   153  		zap.String("tx", txHash),
   154  		zap.String("script", ic.VM.GetCurrentScriptHash().StringLE()),
   155  		zap.String("msg", state))
   156  	return nil
   157  }
   158  
   159  // GetTime returns timestamp of the block being verified, or the latest
   160  // one in the blockchain if no block is given to Context.
   161  func GetTime(ic *interop.Context) error {
   162  	ic.VM.Estack().PushItem(stackitem.NewBigInteger(new(big.Int).SetUint64(ic.Block.Timestamp)))
   163  	return nil
   164  }
   165  
   166  // BurnGas burns GAS to benefit Neo ecosystem.
   167  func BurnGas(ic *interop.Context) error {
   168  	gas := ic.VM.Estack().Pop().BigInt()
   169  	if !gas.IsInt64() {
   170  		return errors.New("invalid GAS value")
   171  	}
   172  
   173  	g := gas.Int64()
   174  	if g <= 0 {
   175  		return errors.New("GAS must be positive")
   176  	}
   177  
   178  	if !ic.VM.AddGas(g) {
   179  		return errors.New("GAS limit exceeded")
   180  	}
   181  	return nil
   182  }
   183  
   184  // CurrentSigners returns signers of the currently loaded transaction or stackitem.Null
   185  // if script container is not a transaction.
   186  func CurrentSigners(ic *interop.Context) error {
   187  	tx, ok := ic.Container.(*transaction.Transaction)
   188  	if ok {
   189  		ic.VM.Estack().PushItem(transaction.SignersToStackItem(tx.Signers))
   190  	} else {
   191  		ic.VM.Estack().PushItem(stackitem.Null{})
   192  	}
   193  
   194  	return nil
   195  }