mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-23 03:38:35 +00:00
Merge pull request #1048 from nspcc-dev/feature/json
Implement System.Json.* interops
This commit is contained in:
commit
3cb3c95aff
14 changed files with 477 additions and 37 deletions
|
@ -4,7 +4,7 @@ import (
|
|||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
var assignTestCases = []testCase{
|
||||
|
@ -144,9 +144,9 @@ func TestManyAssignments(t *testing.T) {
|
|||
src2 := `return a
|
||||
}`
|
||||
|
||||
for i := 0; i < vm.MaxArraySize; i++ {
|
||||
for i := 0; i < stackitem.MaxArraySize; i++ {
|
||||
src1 += "a += 1\n"
|
||||
}
|
||||
|
||||
eval(t, src1+src2, big.NewInt(vm.MaxArraySize))
|
||||
eval(t, src1+src2, big.NewInt(stackitem.MaxArraySize))
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ var syscalls = map[string]map[string]string{
|
|||
"Next": "System.Enumerator.Next",
|
||||
"Value": "System.Enumerator.Value",
|
||||
},
|
||||
"json": {
|
||||
"Serialize": "System.Json.Serialize",
|
||||
"Deserialize": "System.Json.Deserialize",
|
||||
},
|
||||
"storage": {
|
||||
"ConvertContextToReadOnly": "System.Storage.AsReadOnly",
|
||||
"Delete": "System.Storage.Delete",
|
||||
|
|
29
pkg/core/interop/json/json.go
Normal file
29
pkg/core/interop/json/json.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// Serialize handles System.JSON.Serialize syscall.
|
||||
func Serialize(_ *interop.Context, v *vm.VM) error {
|
||||
item := v.Estack().Pop().Item()
|
||||
data, err := stackitem.ToJSON(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deserialize handles System.JSON.Deserialize syscall.
|
||||
func Deserialize(_ *interop.Context, v *vm.VM) error {
|
||||
data := v.Estack().Pop().Bytes()
|
||||
item, err := stackitem.FromJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Estack().PushVal(item)
|
||||
return nil
|
||||
}
|
68
pkg/core/interop/json/json_test.go
Normal file
68
pkg/core/interop/json/json_test.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
serializeID = emit.InteropNameToID([]byte("System.Json.Serialize"))
|
||||
deserializeID = emit.InteropNameToID([]byte("System.Json.Deserialize"))
|
||||
)
|
||||
|
||||
func getInterop(id uint32) *vm.InteropFuncPrice {
|
||||
switch id {
|
||||
case serializeID:
|
||||
return &vm.InteropFuncPrice{
|
||||
Func: func(v *vm.VM) error { return Serialize(nil, v) },
|
||||
}
|
||||
case deserializeID:
|
||||
return &vm.InteropFuncPrice{
|
||||
Func: func(v *vm.VM) error { return Deserialize(nil, v) },
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getTestFunc(id uint32, arg interface{}, result interface{}) func(t *testing.T) {
|
||||
prog := make([]byte, 5)
|
||||
prog[0] = byte(opcode.SYSCALL)
|
||||
binary.LittleEndian.PutUint32(prog[1:], id)
|
||||
|
||||
return func(t *testing.T) {
|
||||
v := vm.New()
|
||||
v.RegisterInteropGetter(getInterop)
|
||||
v.LoadScript(prog)
|
||||
v.Estack().PushVal(arg)
|
||||
if result == nil {
|
||||
require.Error(t, v.Run())
|
||||
return
|
||||
}
|
||||
require.NoError(t, v.Run())
|
||||
require.Equal(t, stackitem.Make(result), v.Estack().Pop().Item())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerialize(t *testing.T) {
|
||||
t.Run("Serialize", func(t *testing.T) {
|
||||
t.Run("Good", getTestFunc(serializeID, 42, []byte("42")))
|
||||
t.Run("Bad", func(t *testing.T) {
|
||||
arr := stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(make([]byte, stackitem.MaxSize/2)),
|
||||
stackitem.NewByteArray(make([]byte, stackitem.MaxSize/2)),
|
||||
})
|
||||
getTestFunc(serializeID, arr, nil)(t)
|
||||
})
|
||||
})
|
||||
t.Run("Deserialize", func(t *testing.T) {
|
||||
t.Run("Good", getTestFunc(deserializeID, []byte("42"), 42))
|
||||
t.Run("Bad", getTestFunc(deserializeID, []byte("{]"), nil))
|
||||
})
|
||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/interop/crypto"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/json"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
|
@ -105,6 +106,8 @@ var systemInterops = []interop.Function{
|
|||
{Name: "System.Iterator.Key", Func: iterator.Key, Price: 400},
|
||||
{Name: "System.Iterator.Keys", Func: iterator.Keys, Price: 400},
|
||||
{Name: "System.Iterator.Values", Func: iterator.Values, Price: 400},
|
||||
{Name: "System.Json.Deserialize", Func: json.Deserialize, Price: 500000},
|
||||
{Name: "System.Json.Serialize", Func: json.Serialize, Price: 100000},
|
||||
{Name: "System.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 200, RequiredFlags: smartcontract.AllowStates},
|
||||
{Name: "System.Runtime.GetTime", Func: runtimeGetTime, Price: 1,
|
||||
AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowStates},
|
||||
|
|
28
pkg/interop/json/json.go
Normal file
28
pkg/interop/json/json.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Package json provides various JSON serialization/deserialization routines.
|
||||
*/
|
||||
package json
|
||||
|
||||
// ToJSON serializes value to json. It uses `System.Json.Serialize` syscall.
|
||||
// Serialization format is the following:
|
||||
// []byte -> base64 string
|
||||
// bool -> json boolean
|
||||
// nil -> Null
|
||||
// string -> base64 encoded sequence of underlying bytes
|
||||
// (u)int* -> integer, only value in -2^53..2^53 are allowed
|
||||
// []interface{} -> json array
|
||||
// map[type1]type2 -> json object with string keys marshaled as strings (not base64).
|
||||
func ToJSON(item interface{}) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FromJSON deserializes value from json. It uses `System.Json.Deserialize` syscall.
|
||||
// It performs deserialization as follows:
|
||||
// strings -> []byte (string) from base64
|
||||
// integers -> (u)int* types
|
||||
// null -> interface{}(nil)
|
||||
// arrays -> []interface{}
|
||||
// maps -> map[string]interface{}
|
||||
func FromJSON(data []byte) interface{} {
|
||||
return nil
|
||||
}
|
|
@ -97,7 +97,7 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
|
|||
err = errNoInstParam
|
||||
} else {
|
||||
var n = binary.LittleEndian.Uint32(c.prog[c.nextip : c.nextip+4])
|
||||
if n > MaxItemSize {
|
||||
if n > stackitem.MaxSize {
|
||||
return instr, nil, errors.New("parameter is too big")
|
||||
}
|
||||
numtoread = int(n)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -21,14 +22,14 @@ func getNumOfThingsFromInstr(instr opcode.Opcode, param []byte) (int, bool) {
|
|||
nthings = int(instr-opcode.PUSH1) + 1
|
||||
case instr <= opcode.PUSHINT256:
|
||||
n := bigint.FromBytes(param)
|
||||
if !n.IsInt64() || n.Int64() > MaxArraySize {
|
||||
if !n.IsInt64() || n.Int64() > stackitem.MaxArraySize {
|
||||
return 0, false
|
||||
}
|
||||
nthings = int(n.Int64())
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
if nthings < 1 || nthings > MaxArraySize {
|
||||
if nthings < 1 || nthings > stackitem.MaxArraySize {
|
||||
return 0, false
|
||||
}
|
||||
return nthings, true
|
||||
|
@ -69,7 +70,7 @@ func ParseMultiSigContract(script []byte) (int, [][]byte, bool) {
|
|||
}
|
||||
pubs = append(pubs, param)
|
||||
nkeys++
|
||||
if nkeys > MaxArraySize {
|
||||
if nkeys > stackitem.MaxArraySize {
|
||||
return nsigs, nil, false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ func RuntimeSerialize(vm *VM) error {
|
|||
data, err := stackitem.SerializeItem(item.value)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(data) > MaxItemSize {
|
||||
} else if len(data) > stackitem.MaxSize {
|
||||
return errors.New("too big item")
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,12 @@ import (
|
|||
// MaxBigIntegerSizeBits is the maximum size of BigInt item in bits.
|
||||
const MaxBigIntegerSizeBits = 32 * 8
|
||||
|
||||
// MaxArraySize is the maximum array size allowed in the VM.
|
||||
const MaxArraySize = 1024
|
||||
|
||||
// MaxSize is the maximum item size allowed in the VM.
|
||||
const MaxSize = 1024 * 1024
|
||||
|
||||
// Item represents the "real" value that is pushed on the stack.
|
||||
type Item interface {
|
||||
fmt.Stringer
|
||||
|
|
202
pkg/vm/stackitem/json.go
Normal file
202
pkg/vm/stackitem/json.go
Normal file
|
@ -0,0 +1,202 @@
|
|||
package stackitem
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
gio "io"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
)
|
||||
|
||||
// decoder is a wrapper around json.Decoder helping to mimic C# json decoder behaviour.
|
||||
type decoder struct {
|
||||
json.Decoder
|
||||
|
||||
depth int
|
||||
}
|
||||
|
||||
// MaxAllowedInteger is the maximum integer allowed to be encoded.
|
||||
const MaxAllowedInteger = 2<<53 - 1
|
||||
|
||||
// maxJSONDepth is a maximum allowed depth-level of decoded JSON.
|
||||
const maxJSONDepth = 10
|
||||
|
||||
// ToJSON encodes Item to JSON.
|
||||
// It behaves as following:
|
||||
// ByteArray -> base64 string
|
||||
// BigInteger -> number
|
||||
// Bool -> bool
|
||||
// Null -> null
|
||||
// Array, Struct -> array
|
||||
// Map -> map with keys as UTF-8 bytes
|
||||
func ToJSON(item Item) ([]byte, error) {
|
||||
buf := io.NewBufBinWriter()
|
||||
toJSON(buf, item)
|
||||
if buf.Err != nil {
|
||||
return nil, buf.Err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func toJSON(buf *io.BufBinWriter, item Item) {
|
||||
w := buf.BinWriter
|
||||
if w.Err != nil {
|
||||
return
|
||||
} else if buf.Len() > MaxSize {
|
||||
w.Err = errors.New("item is too big")
|
||||
}
|
||||
switch it := item.(type) {
|
||||
case *Array, *Struct:
|
||||
w.WriteB('[')
|
||||
items := it.Value().([]Item)
|
||||
for i, v := range items {
|
||||
toJSON(buf, v)
|
||||
if i < len(items)-1 {
|
||||
w.WriteB(',')
|
||||
}
|
||||
}
|
||||
w.WriteB(']')
|
||||
case *Map:
|
||||
w.WriteB('{')
|
||||
for i := range it.value {
|
||||
bs, _ := it.value[i].Key.TryBytes() // map key can always be converted to []byte
|
||||
w.WriteB('"')
|
||||
w.WriteBytes(bs)
|
||||
w.WriteBytes([]byte(`":`))
|
||||
toJSON(buf, it.value[i].Value)
|
||||
if i < len(it.value)-1 {
|
||||
w.WriteB(',')
|
||||
}
|
||||
}
|
||||
w.WriteB('}')
|
||||
case *BigInteger:
|
||||
if it.value.CmpAbs(big.NewInt(MaxAllowedInteger)) == 1 {
|
||||
w.Err = errors.New("too big integer")
|
||||
return
|
||||
}
|
||||
w.WriteBytes([]byte(it.value.String()))
|
||||
case *ByteArray:
|
||||
w.WriteB('"')
|
||||
val := it.Value().([]byte)
|
||||
b := make([]byte, base64.StdEncoding.EncodedLen(len(val)))
|
||||
base64.StdEncoding.Encode(b, val)
|
||||
w.WriteBytes(b)
|
||||
w.WriteB('"')
|
||||
case *Bool:
|
||||
if it.value {
|
||||
w.WriteBytes([]byte("true"))
|
||||
} else {
|
||||
w.WriteBytes([]byte("false"))
|
||||
}
|
||||
case Null:
|
||||
w.WriteBytes([]byte("null"))
|
||||
default:
|
||||
w.Err = fmt.Errorf("invalid item: %s", it.String())
|
||||
return
|
||||
}
|
||||
if w.Err == nil && buf.Len() > MaxSize {
|
||||
w.Err = errors.New("item is too big")
|
||||
}
|
||||
}
|
||||
|
||||
// FromJSON decodes Item from JSON.
|
||||
// It behaves as following:
|
||||
// string -> ByteArray from base64
|
||||
// number -> BigInteger
|
||||
// bool -> Bool
|
||||
// null -> Null
|
||||
// array -> Array
|
||||
// map -> Map, keys are UTF-8
|
||||
func FromJSON(data []byte) (Item, error) {
|
||||
d := decoder{Decoder: *json.NewDecoder(bytes.NewReader(data))}
|
||||
if item, err := d.decode(); err != nil {
|
||||
return nil, err
|
||||
} else if _, err := d.Token(); err != gio.EOF {
|
||||
return nil, errors.New("unexpected items")
|
||||
} else {
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decode() (Item, error) {
|
||||
tok, err := d.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case json.Delim:
|
||||
switch t {
|
||||
case json.Delim('{'), json.Delim('['):
|
||||
if d.depth == maxJSONDepth {
|
||||
return nil, errors.New("JSON depth limit exceeded")
|
||||
}
|
||||
d.depth++
|
||||
var item Item
|
||||
if t == json.Delim('{') {
|
||||
item, err = d.decodeMap()
|
||||
} else {
|
||||
item, err = d.decodeArray()
|
||||
}
|
||||
d.depth--
|
||||
return item, err
|
||||
default:
|
||||
// no error above means corresponding closing token
|
||||
// was encountered for map or array respectively
|
||||
return nil, nil
|
||||
}
|
||||
case string:
|
||||
b, err := base64.StdEncoding.DecodeString(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewByteArray(b), nil
|
||||
case float64:
|
||||
if math.Floor(t) != t {
|
||||
return nil, fmt.Errorf("real value is not allowed: %v", t)
|
||||
}
|
||||
return NewBigInteger(big.NewInt(int64(t))), nil
|
||||
case bool:
|
||||
return NewBool(t), nil
|
||||
default:
|
||||
// it can be only `nil`
|
||||
return Null{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decodeArray() (*Array, error) {
|
||||
items := []Item{}
|
||||
for {
|
||||
item, err := d.decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if item == nil {
|
||||
return NewArray(items), nil
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decodeMap() (*Map, error) {
|
||||
m := NewMap()
|
||||
for {
|
||||
key, err := d.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k, ok := key.(string)
|
||||
if !ok {
|
||||
return m, nil
|
||||
}
|
||||
val, err := d.decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.Add(NewByteArray([]byte(k)), val)
|
||||
}
|
||||
}
|
105
pkg/vm/stackitem/json_test.go
Normal file
105
pkg/vm/stackitem/json_test.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package stackitem
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getTestDecodeFunc(js string, expected ...interface{}) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
actual, err := FromJSON([]byte(js))
|
||||
if expected[0] == nil {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, Make(expected[0]), actual)
|
||||
|
||||
if len(expected) == 1 {
|
||||
encoded, err := ToJSON(actual)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, js, string(encoded))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromToJSON(t *testing.T) {
|
||||
var testBase64 = base64.StdEncoding.EncodeToString([]byte("test"))
|
||||
t.Run("ByteString", func(t *testing.T) {
|
||||
t.Run("Empty", getTestDecodeFunc(`""`, []byte{}))
|
||||
t.Run("Base64", getTestDecodeFunc(`"`+testBase64+`"`, "test"))
|
||||
})
|
||||
t.Run("BigInteger", func(t *testing.T) {
|
||||
t.Run("ZeroFloat", getTestDecodeFunc(`12.000`, 12, nil))
|
||||
t.Run("NonZeroFloat", getTestDecodeFunc(`12.01`, nil))
|
||||
t.Run("Negative", getTestDecodeFunc(`-4`, -4))
|
||||
t.Run("Positive", getTestDecodeFunc(`123`, 123))
|
||||
})
|
||||
t.Run("Bool", func(t *testing.T) {
|
||||
t.Run("True", getTestDecodeFunc(`true`, true))
|
||||
t.Run("False", getTestDecodeFunc(`false`, false))
|
||||
})
|
||||
t.Run("Null", getTestDecodeFunc(`null`, Null{}))
|
||||
t.Run("Array", func(t *testing.T) {
|
||||
t.Run("Empty", getTestDecodeFunc(`[]`, NewArray([]Item{})))
|
||||
t.Run("Simple", getTestDecodeFunc((`[1,"`+testBase64+`",true,null]`),
|
||||
NewArray([]Item{NewBigInteger(big.NewInt(1)), NewByteArray([]byte("test")), NewBool(true), Null{}})))
|
||||
t.Run("Nested", getTestDecodeFunc(`[[],[{},null]]`,
|
||||
NewArray([]Item{NewArray([]Item{}), NewArray([]Item{NewMap(), Null{}})})))
|
||||
})
|
||||
t.Run("Map", func(t *testing.T) {
|
||||
small := NewMap()
|
||||
small.Add(NewByteArray([]byte("a")), NewBigInteger(big.NewInt(3)))
|
||||
large := NewMap()
|
||||
large.Add(NewByteArray([]byte("3")), small)
|
||||
large.Add(NewByteArray([]byte("arr")), NewArray([]Item{NewByteArray([]byte("test"))}))
|
||||
t.Run("Empty", getTestDecodeFunc(`{}`, NewMap()))
|
||||
t.Run("Small", getTestDecodeFunc(`{"a":3}`, small))
|
||||
t.Run("Big", getTestDecodeFunc(`{"3":{"a":3},"arr":["`+testBase64+`"]}`, large))
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
t.Run("Empty", getTestDecodeFunc(``, nil))
|
||||
t.Run("InvalidString", getTestDecodeFunc(`"not a base64"`, nil))
|
||||
t.Run("InvalidArray", getTestDecodeFunc(`[}`, nil))
|
||||
t.Run("InvalidMap", getTestDecodeFunc(`{]`, nil))
|
||||
t.Run("InvalidMapValue", getTestDecodeFunc(`{"a":{]}`, nil))
|
||||
t.Run("AfterArray", getTestDecodeFunc(`[]XX`, nil))
|
||||
t.Run("EncodeBigInteger", func(t *testing.T) {
|
||||
item := NewBigInteger(big.NewInt(MaxAllowedInteger + 1))
|
||||
_, err := ToJSON(item)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("EncodeInvalidItemType", func(t *testing.T) {
|
||||
item := NewPointer(1, []byte{1, 2, 3})
|
||||
_, err := ToJSON(item)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("BigByteArray", func(t *testing.T) {
|
||||
l := base64.StdEncoding.DecodedLen(MaxSize + 8)
|
||||
require.True(t, l < MaxSize) // check if test makes sense
|
||||
item := NewByteArray(make([]byte, l))
|
||||
_, err := ToJSON(item)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("BigNestedArray", getTestDecodeFunc(`[[[[[[[[[[[]]]]]]]]]]]`, nil))
|
||||
t.Run("EncodeRecursive", func(t *testing.T) {
|
||||
// add this item to speed up test a bit
|
||||
item := NewByteArray(make([]byte, MaxSize/100))
|
||||
t.Run("Array", func(t *testing.T) {
|
||||
arr := NewArray([]Item{item})
|
||||
arr.Append(arr)
|
||||
_, err := ToJSON(arr)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Map", func(t *testing.T) {
|
||||
m := NewMap()
|
||||
m.Add(item, m)
|
||||
_, err := ToJSON(m)
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
22
pkg/vm/vm.go
22
pkg/vm/vm.go
|
@ -46,12 +46,6 @@ type ScriptHashGetter interface {
|
|||
}
|
||||
|
||||
const (
|
||||
// MaxArraySize is the maximum array size allowed in the VM.
|
||||
MaxArraySize = 1024
|
||||
|
||||
// MaxItemSize is the maximum item size allowed in the VM.
|
||||
MaxItemSize = 1024 * 1024
|
||||
|
||||
// MaxInvocationStackSize is the maximum size of an invocation stack.
|
||||
MaxInvocationStackSize = 1024
|
||||
|
||||
|
@ -653,7 +647,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
|
||||
case opcode.NEWBUFFER:
|
||||
n := toInt(v.estack.Pop().BigInt())
|
||||
if n < 0 || n > MaxItemSize {
|
||||
if n < 0 || n > stackitem.MaxSize {
|
||||
panic("invalid size")
|
||||
}
|
||||
v.estack.PushVal(stackitem.NewBuffer(make([]byte, n)))
|
||||
|
@ -684,7 +678,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
case opcode.CAT:
|
||||
b := v.estack.Pop().Bytes()
|
||||
a := v.estack.Pop().Bytes()
|
||||
if l := len(a) + len(b); l > MaxItemSize {
|
||||
if l := len(a) + len(b); l > stackitem.MaxSize {
|
||||
panic(fmt.Sprintf("too big item: %d", l))
|
||||
}
|
||||
ab := append(a, b...)
|
||||
|
@ -1001,7 +995,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
case opcode.NEWARRAY, opcode.NEWARRAYT:
|
||||
item := v.estack.Pop()
|
||||
n := item.BigInt().Int64()
|
||||
if n > MaxArraySize {
|
||||
if n > stackitem.MaxArraySize {
|
||||
panic("too long array")
|
||||
}
|
||||
typ := stackitem.AnyT
|
||||
|
@ -1017,7 +1011,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
case opcode.NEWSTRUCT:
|
||||
item := v.estack.Pop()
|
||||
n := item.BigInt().Int64()
|
||||
if n > MaxArraySize {
|
||||
if n > stackitem.MaxArraySize {
|
||||
panic("too long struct")
|
||||
}
|
||||
items := makeArrayOfType(int(n), stackitem.AnyT)
|
||||
|
@ -1031,12 +1025,12 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
|
||||
switch t := arrElem.value.(type) {
|
||||
case *stackitem.Array:
|
||||
if t.Len() >= MaxArraySize {
|
||||
if t.Len() >= stackitem.MaxArraySize {
|
||||
panic("too long array")
|
||||
}
|
||||
t.Append(val)
|
||||
case *stackitem.Struct:
|
||||
if t.Len() >= MaxArraySize {
|
||||
if t.Len() >= stackitem.MaxArraySize {
|
||||
panic("too long struct")
|
||||
}
|
||||
t.Append(val)
|
||||
|
@ -1048,7 +1042,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
|
||||
case opcode.PACK:
|
||||
n := int(v.estack.Pop().BigInt().Int64())
|
||||
if n < 0 || n > v.estack.Len() || n > MaxArraySize {
|
||||
if n < 0 || n > v.estack.Len() || n > stackitem.MaxArraySize {
|
||||
panic("OPACK: invalid length")
|
||||
}
|
||||
|
||||
|
@ -1119,7 +1113,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
|
|||
case *stackitem.Map:
|
||||
if i := t.Index(key.value); i >= 0 {
|
||||
v.refs.Remove(t.Value().([]stackitem.MapElement)[i].Value)
|
||||
} else if t.Len() >= MaxArraySize {
|
||||
} else if t.Len() >= stackitem.MaxArraySize {
|
||||
panic("too big map")
|
||||
}
|
||||
t.Add(key.value, item)
|
||||
|
|
|
@ -489,9 +489,9 @@ func TestPUSHDATA4(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPushData4BigN(t *testing.T) {
|
||||
prog := make([]byte, 1+4+MaxItemSize+1)
|
||||
prog := make([]byte, 1+4+stackitem.MaxSize+1)
|
||||
prog[0] = byte(opcode.PUSHDATA4)
|
||||
binary.LittleEndian.PutUint32(prog[1:], MaxItemSize+1)
|
||||
binary.LittleEndian.PutUint32(prog[1:], stackitem.MaxSize+1)
|
||||
|
||||
vm := load(prog)
|
||||
checkVMFailed(t, vm)
|
||||
|
@ -1222,7 +1222,7 @@ func TestNEWBUFFER(t *testing.T) {
|
|||
prog := makeProgram(opcode.NEWBUFFER)
|
||||
t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte{0, 0, 0}), 3))
|
||||
t.Run("Negative", getTestFuncForVM(prog, nil, -1))
|
||||
t.Run("TooBig", getTestFuncForVM(prog, nil, MaxItemSize+1))
|
||||
t.Run("TooBig", getTestFuncForVM(prog, nil, stackitem.MaxSize+1))
|
||||
}
|
||||
|
||||
func TestMEMCPY(t *testing.T) {
|
||||
|
@ -1251,7 +1251,7 @@ func TestNEWSTRUCT0(t *testing.T) {
|
|||
func TestNEWARRAYArray(t *testing.T) {
|
||||
prog := makeProgram(opcode.NEWARRAY)
|
||||
t.Run("ByteArray", getTestFuncForVM(prog, stackitem.NewArray([]stackitem.Item{}), []byte{}))
|
||||
t.Run("BadSize", getTestFuncForVM(prog, nil, MaxArraySize+1))
|
||||
t.Run("BadSize", getTestFuncForVM(prog, nil, stackitem.MaxArraySize+1))
|
||||
t.Run("Integer", getTestFuncForVM(prog, []stackitem.Item{stackitem.Null{}}, 1))
|
||||
}
|
||||
|
||||
|
@ -1302,7 +1302,7 @@ func TestNEWARRAYT(t *testing.T) {
|
|||
func TestNEWSTRUCT(t *testing.T) {
|
||||
prog := makeProgram(opcode.NEWSTRUCT)
|
||||
t.Run("ByteArray", getTestFuncForVM(prog, stackitem.NewStruct([]stackitem.Item{}), []byte{}))
|
||||
t.Run("BadSize", getTestFuncForVM(prog, nil, MaxArraySize+1))
|
||||
t.Run("BadSize", getTestFuncForVM(prog, nil, stackitem.MaxArraySize+1))
|
||||
t.Run("Integer", getTestFuncForVM(prog, stackitem.NewStruct([]stackitem.Item{stackitem.Null{}}), 1))
|
||||
}
|
||||
|
||||
|
@ -1330,15 +1330,15 @@ func TestAPPENDBad(t *testing.T) {
|
|||
func TestAPPENDGoodSizeLimit(t *testing.T) {
|
||||
prog := makeProgram(opcode.NEWARRAY, opcode.DUP, opcode.PUSH0, opcode.APPEND)
|
||||
vm := load(prog)
|
||||
vm.estack.PushVal(MaxArraySize - 1)
|
||||
vm.estack.PushVal(stackitem.MaxArraySize - 1)
|
||||
runVM(t, vm)
|
||||
assert.Equal(t, 1, vm.estack.Len())
|
||||
assert.Equal(t, MaxArraySize, len(vm.estack.Pop().Array()))
|
||||
assert.Equal(t, stackitem.MaxArraySize, len(vm.estack.Pop().Array()))
|
||||
}
|
||||
|
||||
func TestAPPENDBadSizeLimit(t *testing.T) {
|
||||
prog := makeProgram(opcode.NEWARRAY, opcode.DUP, opcode.PUSH0, opcode.APPEND)
|
||||
runWithArgs(t, prog, nil, MaxArraySize)
|
||||
runWithArgs(t, prog, nil, stackitem.MaxArraySize)
|
||||
}
|
||||
|
||||
func TestPICKITEM(t *testing.T) {
|
||||
|
@ -1399,11 +1399,11 @@ func TestSETITEMMap(t *testing.T) {
|
|||
func TestSETITEMBigMapBad(t *testing.T) {
|
||||
prog := makeProgram(opcode.SETITEM)
|
||||
m := stackitem.NewMap()
|
||||
for i := 0; i < MaxArraySize; i++ {
|
||||
for i := 0; i < stackitem.MaxArraySize; i++ {
|
||||
m.Add(stackitem.Make(i), stackitem.Make(i))
|
||||
}
|
||||
|
||||
runWithArgs(t, prog, nil, m, MaxArraySize, 0)
|
||||
runWithArgs(t, prog, nil, m, stackitem.MaxArraySize, 0)
|
||||
}
|
||||
|
||||
// This test checks is SETITEM properly updates reference counter.
|
||||
|
@ -1411,7 +1411,7 @@ func TestSETITEMBigMapBad(t *testing.T) {
|
|||
// 2. SETITEM each of them to a map.
|
||||
// 3. Replace each of them with a scalar value.
|
||||
func TestSETITEMMapStackLimit(t *testing.T) {
|
||||
size := MaxArraySize - 3
|
||||
size := stackitem.MaxArraySize - 3
|
||||
m := stackitem.NewMap()
|
||||
m.Add(stackitem.NewBigInteger(big.NewInt(1)), stackitem.NewArray(makeArrayOfType(size, stackitem.BooleanT)))
|
||||
m.Add(stackitem.NewBigInteger(big.NewInt(2)), stackitem.NewArray(makeArrayOfType(size, stackitem.BooleanT)))
|
||||
|
@ -1431,7 +1431,7 @@ func TestSETITEMBigMapGood(t *testing.T) {
|
|||
vm := load(prog)
|
||||
|
||||
m := stackitem.NewMap()
|
||||
for i := 0; i < MaxArraySize; i++ {
|
||||
for i := 0; i < stackitem.MaxArraySize; i++ {
|
||||
m.Add(stackitem.Make(i), stackitem.Make(i))
|
||||
}
|
||||
vm.estack.Push(&Element{value: m})
|
||||
|
@ -1842,7 +1842,7 @@ func TestCAT(t *testing.T) {
|
|||
t.Run("NoArgument", getTestFuncForVM(prog, nil))
|
||||
t.Run("OneArgument", getTestFuncForVM(prog, nil, []byte("abc")))
|
||||
t.Run("BigItem", func(t *testing.T) {
|
||||
arg := make([]byte, MaxItemSize/2+1)
|
||||
arg := make([]byte, stackitem.MaxSize/2+1)
|
||||
runWithArgs(t, prog, nil, arg, arg)
|
||||
})
|
||||
t.Run("Good", getTestFuncForVM(prog, stackitem.NewBuffer([]byte("abcdef")), []byte("abc"), []byte("def")))
|
||||
|
@ -1900,10 +1900,10 @@ func TestPACK(t *testing.T) {
|
|||
func TestPACKBigLen(t *testing.T) {
|
||||
prog := makeProgram(opcode.PACK)
|
||||
vm := load(prog)
|
||||
for i := 0; i <= MaxArraySize; i++ {
|
||||
for i := 0; i <= stackitem.MaxArraySize; i++ {
|
||||
vm.estack.PushVal(0)
|
||||
}
|
||||
vm.estack.PushVal(MaxArraySize + 1)
|
||||
vm.estack.PushVal(stackitem.MaxArraySize + 1)
|
||||
checkVMFailed(t, vm)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue