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
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"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 {
return ic . VM . PushContextScriptHash ( ic . VM . Istack ( ) . Len ( ) - 1 )
}
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 )
}
2022-09-21 21:01:23 +00:00
curHash := ic . VM . GetCurrentScriptHash ( )
ctr , err := ic . GetContract ( curHash )
if err != nil {
2022-07-27 11:49:53 +00:00
return errors . New ( "notifications are not allowed in dynamic scripts" )
}
2022-09-21 21:01:23 +00:00
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 ) )
}
}
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
}
// 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
// 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
}