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 }