233307aca5
Turns out C# VM doesn't have it since preview2, so our limiting of MaxArraySize in incompatible with it. Removing this limit shouldn't be a problem with the reference counter we have, both APPEND and SETITEM add things to reference counter and we can't exceed MaxStackSize. PACK on the other hand can't get more than MaxStackSize-1 of input elements. Unify NEWSTRUCT with NEWARRAY* and use better integer checks at the same time. Multisig limit is still 1024.
253 lines
6.9 KiB
Go
253 lines
6.9 KiB
Go
package state
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// NotificationEvent is a tuple of scripthash that emitted the Item as a
|
|
// notification and that item itself.
|
|
type NotificationEvent struct {
|
|
ScriptHash util.Uint160 `json:"contract"`
|
|
Name string `json:"eventname"`
|
|
Item *stackitem.Array `json:"state"`
|
|
}
|
|
|
|
// AppExecResult represent the result of the script execution, gathering together
|
|
// all resulting notifications, state, stack and other metadata.
|
|
type AppExecResult struct {
|
|
Container util.Uint256
|
|
Execution
|
|
}
|
|
|
|
// EncodeBinary implements the Serializable interface.
|
|
func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) {
|
|
ne.ScriptHash.EncodeBinary(w)
|
|
w.WriteString(ne.Name)
|
|
stackitem.EncodeBinary(ne.Item, w)
|
|
}
|
|
|
|
// DecodeBinary implements the Serializable interface.
|
|
func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) {
|
|
ne.ScriptHash.DecodeBinary(r)
|
|
ne.Name = r.ReadString()
|
|
item := stackitem.DecodeBinary(r)
|
|
if r.Err != nil {
|
|
return
|
|
}
|
|
arr, ok := item.Value().([]stackitem.Item)
|
|
if !ok {
|
|
r.Err = errors.New("Array or Struct expected")
|
|
return
|
|
}
|
|
ne.Item = stackitem.NewArray(arr)
|
|
}
|
|
|
|
// EncodeBinary implements the Serializable interface.
|
|
func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
|
w.WriteBytes(aer.Container[:])
|
|
w.WriteB(byte(aer.Trigger))
|
|
w.WriteB(byte(aer.VMState))
|
|
w.WriteU64LE(uint64(aer.GasConsumed))
|
|
// Stack items are expected to be marshaled one by one.
|
|
w.WriteVarUint(uint64(len(aer.Stack)))
|
|
for _, it := range aer.Stack {
|
|
stackitem.EncodeBinaryProtected(it, w)
|
|
}
|
|
w.WriteArray(aer.Events)
|
|
w.WriteVarBytes([]byte(aer.FaultException))
|
|
}
|
|
|
|
// DecodeBinary implements the Serializable interface.
|
|
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
|
r.ReadBytes(aer.Container[:])
|
|
aer.Trigger = trigger.Type(r.ReadB())
|
|
aer.VMState = vm.State(r.ReadB())
|
|
aer.GasConsumed = int64(r.ReadU64LE())
|
|
sz := r.ReadVarUint()
|
|
if stackitem.MaxDeserialized < sz && r.Err == nil {
|
|
r.Err = errors.New("invalid format")
|
|
}
|
|
if r.Err != nil {
|
|
return
|
|
}
|
|
arr := make([]stackitem.Item, sz)
|
|
for i := 0; i < int(sz); i++ {
|
|
arr[i] = stackitem.DecodeBinaryProtected(r)
|
|
if r.Err != nil {
|
|
return
|
|
}
|
|
}
|
|
aer.Stack = arr
|
|
r.ReadArray(&aer.Events)
|
|
aer.FaultException = r.ReadString()
|
|
}
|
|
|
|
// notificationEventAux is an auxiliary struct for NotificationEvent JSON marshalling.
|
|
type notificationEventAux struct {
|
|
ScriptHash util.Uint160 `json:"contract"`
|
|
Name string `json:"eventname"`
|
|
Item json.RawMessage `json:"state"`
|
|
}
|
|
|
|
// MarshalJSON implements implements json.Marshaler interface.
|
|
func (ne NotificationEvent) MarshalJSON() ([]byte, error) {
|
|
item, err := stackitem.ToJSONWithTypes(ne.Item)
|
|
if err != nil {
|
|
item = []byte(fmt.Sprintf(`"error: %v"`, err))
|
|
}
|
|
return json.Marshal(¬ificationEventAux{
|
|
ScriptHash: ne.ScriptHash,
|
|
Name: ne.Name,
|
|
Item: item,
|
|
})
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
|
func (ne *NotificationEvent) UnmarshalJSON(data []byte) error {
|
|
aux := new(notificationEventAux)
|
|
if err := json.Unmarshal(data, aux); err != nil {
|
|
return err
|
|
}
|
|
item, err := stackitem.FromJSONWithTypes(aux.Item)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if t := item.Type(); t != stackitem.ArrayT {
|
|
return fmt.Errorf("failed to convert notification event state of type %s to array", t.String())
|
|
}
|
|
ne.Item = item.(*stackitem.Array)
|
|
ne.Name = aux.Name
|
|
ne.ScriptHash = aux.ScriptHash
|
|
return nil
|
|
}
|
|
|
|
// appExecResultAux is an auxiliary struct for JSON marshalling.
|
|
type appExecResultAux struct {
|
|
Container util.Uint256 `json:"container"`
|
|
}
|
|
|
|
// MarshalJSON implements implements json.Marshaler interface.
|
|
func (aer *AppExecResult) MarshalJSON() ([]byte, error) {
|
|
h, err := json.Marshal(&appExecResultAux{
|
|
Container: aer.Container,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal hash: %w", err)
|
|
}
|
|
exec, err := json.Marshal(aer.Execution)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal execution: %w", err)
|
|
}
|
|
|
|
if h[len(h)-1] != '}' || exec[0] != '{' {
|
|
return nil, errors.New("can't merge internal jsons")
|
|
}
|
|
h[len(h)-1] = ','
|
|
h = append(h, exec[1:]...)
|
|
return h, nil
|
|
}
|
|
|
|
// UnmarshalJSON implements implements json.Unmarshaler interface.
|
|
func (aer *AppExecResult) UnmarshalJSON(data []byte) error {
|
|
aux := new(appExecResultAux)
|
|
if err := json.Unmarshal(data, aux); err != nil {
|
|
return err
|
|
}
|
|
if err := json.Unmarshal(data, &aer.Execution); err != nil {
|
|
return err
|
|
}
|
|
aer.Container = aux.Container
|
|
return nil
|
|
}
|
|
|
|
// Execution represents the result of a single script execution, gathering together
|
|
// all resulting notifications, state, stack and other metadata.
|
|
type Execution struct {
|
|
Trigger trigger.Type
|
|
VMState vm.State
|
|
GasConsumed int64
|
|
Stack []stackitem.Item
|
|
Events []NotificationEvent
|
|
FaultException string
|
|
}
|
|
|
|
// executionAux represents an auxiliary struct for Execution JSON marshalling.
|
|
type executionAux struct {
|
|
Trigger string `json:"trigger"`
|
|
VMState string `json:"vmstate"`
|
|
GasConsumed int64 `json:"gasconsumed,string"`
|
|
Stack json.RawMessage `json:"stack"`
|
|
Events []NotificationEvent `json:"notifications"`
|
|
FaultException string `json:"exception,omitempty"`
|
|
}
|
|
|
|
// MarshalJSON implements implements json.Marshaler interface.
|
|
func (e Execution) MarshalJSON() ([]byte, error) {
|
|
arr := make([]json.RawMessage, len(e.Stack))
|
|
for i := range arr {
|
|
data, err := stackitem.ToJSONWithTypes(e.Stack[i])
|
|
if err != nil {
|
|
data = []byte(fmt.Sprintf(`"error: %v"`, err))
|
|
}
|
|
arr[i] = data
|
|
}
|
|
st, err := json.Marshal(arr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return json.Marshal(&executionAux{
|
|
Trigger: e.Trigger.String(),
|
|
VMState: e.VMState.String(),
|
|
GasConsumed: e.GasConsumed,
|
|
Stack: st,
|
|
Events: e.Events,
|
|
FaultException: e.FaultException,
|
|
})
|
|
}
|
|
|
|
// UnmarshalJSON implements implements json.Unmarshaler interface.
|
|
func (e *Execution) UnmarshalJSON(data []byte) error {
|
|
aux := new(executionAux)
|
|
if err := json.Unmarshal(data, aux); err != nil {
|
|
return err
|
|
}
|
|
var arr []json.RawMessage
|
|
if err := json.Unmarshal(aux.Stack, &arr); err == nil {
|
|
st := make([]stackitem.Item, len(arr))
|
|
for i := range arr {
|
|
st[i], err = stackitem.FromJSONWithTypes(arr[i])
|
|
if err != nil {
|
|
var s string
|
|
if json.Unmarshal(arr[i], &s) != nil {
|
|
break
|
|
}
|
|
err = nil
|
|
}
|
|
}
|
|
if err == nil {
|
|
e.Stack = st
|
|
}
|
|
}
|
|
trigger, err := trigger.FromString(aux.Trigger)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Trigger = trigger
|
|
state, err := vm.StateFromString(aux.VMState)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.VMState = state
|
|
e.Events = aux.Events
|
|
e.GasConsumed = aux.GasConsumed
|
|
e.FaultException = aux.FaultException
|
|
return nil
|
|
}
|