-
Notifications
You must be signed in to change notification settings - Fork 77
/
engine.go
195 lines (174 loc) · 5.86 KB
/
engine.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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)
}
curHash := ic.VM.GetCurrentScriptHash()
ctr, err := ic.GetContract(curHash)
if err != nil {
return errors.New("notifications are not allowed in dynamic scripts")
}
var (
ev = ctr.Manifest.ABI.GetEvent(name)
checkErr error
)
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
}