diff --git a/pkg/vm/stackitem/json_test.go b/pkg/vm/stackitem/json_test.go index d1ce23fc0..a6e661115 100644 --- a/pkg/vm/stackitem/json_test.go +++ b/pkg/vm/stackitem/json_test.go @@ -1,6 +1,7 @@ package stackitem import ( + "errors" "math/big" "testing" @@ -105,6 +106,42 @@ func TestFromToJSON(t *testing.T) { }) } +func testToJSON(t *testing.T, expectedErr error, item Item) { + data, err := ToJSON(item) + if expectedErr != nil { + require.True(t, errors.Is(err, expectedErr), err) + return + } + require.NoError(t, err) + + actual, err := FromJSON(data) + require.NoError(t, err) + require.Equal(t, item, actual) +} + +func TestToJSONCornerCases(t *testing.T) { + // base64 encoding increases size by a factor of ~256/64 = 4 + const maxSize = MaxSize / 4 + + bigByteArray := NewByteArray(make([]byte, maxSize/2)) + smallByteArray := NewByteArray(make([]byte, maxSize/4)) + t.Run("Array", func(t *testing.T) { + arr := NewArray([]Item{bigByteArray}) + testToJSON(t, ErrTooBig, NewArray([]Item{arr, arr})) + + arr.value[0] = smallByteArray + testToJSON(t, nil, NewArray([]Item{arr, arr})) + }) + t.Run("big ByteArray", func(t *testing.T) { + testToJSON(t, ErrTooBig, NewByteArray(make([]byte, maxSize+4))) + }) + t.Run("invalid Map key", func(t *testing.T) { + m := NewMap() + m.Add(Make([]byte{0xe9}), Make(true)) + testToJSON(t, ErrInvalidValue, m) + }) +} + // getBigArray returns array takes up a lot of storage when serialized. func getBigArray(depth int) *Array { arr := NewArray([]Item{}) diff --git a/pkg/vm/stackitem/serialization_test.go b/pkg/vm/stackitem/serialization_test.go index 34f38820a..b5adff651 100644 --- a/pkg/vm/stackitem/serialization_test.go +++ b/pkg/vm/stackitem/serialization_test.go @@ -23,6 +23,112 @@ func TestSerializationMaxErr(t *testing.T) { require.True(t, errors.Is(err, ErrTooBig), err) } +func testSerialize(t *testing.T, expectedErr error, item Item) { + data, err := Serialize(item) + if expectedErr != nil { + require.True(t, errors.Is(err, expectedErr), err) + return + } + require.NoError(t, err) + + actual, err := Deserialize(data) + require.NoError(t, err) + require.Equal(t, item, actual) +} + +func TestSerialize(t *testing.T) { + bigByteArray := NewByteArray(make([]byte, MaxSize/2)) + smallByteArray := NewByteArray(make([]byte, MaxSize/4)) + testArray := func(t *testing.T, newItem func([]Item) Item) { + arr := newItem([]Item{bigByteArray}) + testSerialize(t, nil, arr) + testSerialize(t, ErrTooBig, newItem([]Item{bigByteArray, bigByteArray})) + testSerialize(t, ErrTooBig, newItem([]Item{arr, arr})) + + arr.Value().([]Item)[0] = smallByteArray + testSerialize(t, nil, newItem([]Item{arr, arr})) + + arr.Value().([]Item)[0] = arr + testSerialize(t, ErrRecursive, arr) + } + t.Run("array", func(t *testing.T) { + testArray(t, func(items []Item) Item { return NewArray(items) }) + }) + t.Run("struct", func(t *testing.T) { + testArray(t, func(items []Item) Item { return NewStruct(items) }) + }) + t.Run("buffer", func(t *testing.T) { + testSerialize(t, nil, NewBuffer(make([]byte, MaxSize/2))) + testSerialize(t, errTooBigSize, NewBuffer(make([]byte, MaxSize))) + }) + t.Run("invalid", func(t *testing.T) { + testSerialize(t, ErrUnserializable, NewInterop(42)) + testSerialize(t, ErrUnserializable, nil) + + t.Run("protected interop", func(t *testing.T) { + w := io.NewBufBinWriter() + EncodeBinaryProtected(NewInterop(42), w.BinWriter) + require.NoError(t, w.Err) + + data := w.Bytes() + r := io.NewBinReaderFromBuf(data) + DecodeBinary(r) + require.Error(t, r.Err) + + r = io.NewBinReaderFromBuf(data) + item := DecodeBinaryProtected(r) + require.NoError(t, r.Err) + require.IsType(t, (*Interop)(nil), item) + }) + t.Run("protected nil", func(t *testing.T) { + w := io.NewBufBinWriter() + EncodeBinaryProtected(nil, w.BinWriter) + require.NoError(t, w.Err) + + data := w.Bytes() + r := io.NewBinReaderFromBuf(data) + DecodeBinary(r) + require.Error(t, r.Err) + + r = io.NewBinReaderFromBuf(data) + item := DecodeBinaryProtected(r) + require.NoError(t, r.Err) + require.Nil(t, item) + }) + }) + t.Run("bool", func(t *testing.T) { + testSerialize(t, nil, NewBool(true)) + testSerialize(t, nil, NewBool(false)) + }) + t.Run("null", func(t *testing.T) { + testSerialize(t, nil, Null{}) + }) + t.Run("integer", func(t *testing.T) { + testSerialize(t, nil, Make(0xF)) // 1-byte + testSerialize(t, nil, Make(0xFAB)) // 2-byte + testSerialize(t, nil, Make(0xFABCD)) // 4-byte + testSerialize(t, nil, Make(0xFABCDEFEDC)) // 8-byte + }) + t.Run("map", func(t *testing.T) { + one := Make(1) + m := NewMap() + m.Add(one, m) + testSerialize(t, ErrRecursive, m) + + m.Add(one, bigByteArray) + testSerialize(t, nil, m) + + m.Add(Make(2), bigByteArray) + testSerialize(t, ErrTooBig, m) + + // Cover code path when result becomes too big after key encode. + m = NewMap() + m.Add(Make(0), NewByteArray(make([]byte, MaxSize-MaxKeySize))) + m.Add(NewByteArray(make([]byte, MaxKeySize)), Make(1)) + testSerialize(t, ErrTooBig, m) + }) +} + func BenchmarkEncodeBinary(b *testing.B) { arr := getBigArray(15)