github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/vm/stackitem/json_test.go (about)

     1  package stackitem
     2  
     3  import (
     4  	"math/big"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  )
    10  
    11  func getTestDecodeFunc(js string, expected ...interface{}) func(t *testing.T) {
    12  	return getTestDecodeEncodeFunc(js, true, expected...)
    13  }
    14  
    15  func getTestDecodeEncodeFunc(js string, needEncode bool, expected ...interface{}) func(t *testing.T) {
    16  	return func(t *testing.T) {
    17  		actual, err := FromJSON([]byte(js), 20, true)
    18  		if expected[0] == nil {
    19  			require.Error(t, err)
    20  			return
    21  		}
    22  		require.NoError(t, err)
    23  		require.Equal(t, Make(expected[0]), actual)
    24  
    25  		if needEncode && len(expected) == 1 {
    26  			encoded, err := ToJSON(actual)
    27  			require.NoError(t, err)
    28  			require.Equal(t, js, string(encoded))
    29  		}
    30  	}
    31  }
    32  
    33  func TestFromToJSON(t *testing.T) {
    34  	bigInt, ok := new(big.Int).SetString("28000000000000000000000", 10)
    35  	require.True(t, ok)
    36  	t.Run("ByteString", func(t *testing.T) {
    37  		t.Run("Empty", getTestDecodeFunc(`""`, []byte{}))
    38  		t.Run("Base64", getTestDecodeFunc(`"test"`, "test"))
    39  		t.Run("Escape", getTestDecodeFunc(`"\"quotes\""`, `"quotes"`))
    40  	})
    41  	t.Run("BigInteger", func(t *testing.T) {
    42  		t.Run("ZeroFloat", getTestDecodeFunc(`12.000`, 12, nil))
    43  		t.Run("NonZeroFloat", getTestDecodeFunc(`12.01`, nil))
    44  		t.Run("ExpInteger", getTestDecodeEncodeFunc(`2.8e+22`, false, bigInt))
    45  		t.Run("ExpFloat", getTestDecodeEncodeFunc(`1.2345e+3`, false, nil)) // float value, parsing should fail for it.
    46  		t.Run("Negative", getTestDecodeFunc(`-4`, -4))
    47  		t.Run("Positive", getTestDecodeFunc(`123`, 123))
    48  	})
    49  	t.Run("Bool", func(t *testing.T) {
    50  		t.Run("True", getTestDecodeFunc(`true`, true))
    51  		t.Run("False", getTestDecodeFunc(`false`, false))
    52  	})
    53  	t.Run("Null", getTestDecodeFunc(`null`, Null{}))
    54  	t.Run("Array", func(t *testing.T) {
    55  		t.Run("Empty", getTestDecodeFunc(`[]`, NewArray([]Item{})))
    56  		t.Run("Simple", getTestDecodeFunc((`[1,"test",true,null]`),
    57  			NewArray([]Item{NewBigInteger(big.NewInt(1)), NewByteArray([]byte("test")), NewBool(true), Null{}})))
    58  		t.Run("Nested", getTestDecodeFunc(`[[],[{},null]]`,
    59  			NewArray([]Item{NewArray([]Item{}), NewArray([]Item{NewMap(), Null{}})})))
    60  		t.Run("ManyElements", func(t *testing.T) {
    61  			js := `[1, 2, 3]` // 3 elements + array itself
    62  			_, err := FromJSON([]byte(js), 4, true)
    63  			require.NoError(t, err)
    64  
    65  			_, err = FromJSON([]byte(js), 3, true)
    66  			require.ErrorIs(t, err, errTooBigElements)
    67  		})
    68  	})
    69  	t.Run("Map", func(t *testing.T) {
    70  		small := NewMap()
    71  		small.Add(NewByteArray([]byte("a")), NewBigInteger(big.NewInt(3)))
    72  		large := NewMap()
    73  		large.Add(NewByteArray([]byte("3")), small)
    74  		large.Add(NewByteArray([]byte("arr")), NewArray([]Item{NewByteArray([]byte("test"))}))
    75  		t.Run("Empty", getTestDecodeFunc(`{}`, NewMap()))
    76  		t.Run("Small", getTestDecodeFunc(`{"a":3}`, small))
    77  		t.Run("Big", getTestDecodeFunc(`{"3":{"a":3},"arr":["test"]}`, large))
    78  
    79  		m := NewMap()
    80  		m.Add(NewByteArray([]byte("\t")), NewBool(true))
    81  		t.Run("escape keys", getTestDecodeFunc(`{"\t":true}`, m))
    82  
    83  		t.Run("ManyElements", func(t *testing.T) {
    84  			js := `{"a":1,"b":3}` // 4 elements + map itself
    85  			_, err := FromJSON([]byte(js), 5, true)
    86  			require.NoError(t, err)
    87  
    88  			_, err = FromJSON([]byte(js), 4, true)
    89  			require.ErrorIs(t, err, errTooBigElements)
    90  		})
    91  	})
    92  	t.Run("Invalid", func(t *testing.T) {
    93  		t.Run("Empty", getTestDecodeFunc(``, nil))
    94  		t.Run("InvalidArray", getTestDecodeFunc(`[}`, nil))
    95  		t.Run("InvalidMap", getTestDecodeFunc(`{]`, nil))
    96  		t.Run("InvalidMapValue", getTestDecodeFunc(`{"a":{]}`, nil))
    97  		t.Run("AfterArray", getTestDecodeFunc(`[]XX`, nil))
    98  		t.Run("EncodeBigInteger", func(t *testing.T) {
    99  			item := NewBigInteger(big.NewInt(MaxAllowedInteger + 1))
   100  			_, err := ToJSON(item)
   101  			require.Error(t, err)
   102  		})
   103  		t.Run("EncodeInvalidItemType", func(t *testing.T) {
   104  			item := NewPointer(1, []byte{1, 2, 3})
   105  			_, err := ToJSON(item)
   106  			require.Error(t, err)
   107  		})
   108  		t.Run("BigByteArray", func(t *testing.T) {
   109  			item := NewByteArray(make([]byte, MaxSize))
   110  			_, err := ToJSON(item)
   111  			require.Error(t, err)
   112  		})
   113  		t.Run("BigNestedArray", getTestDecodeFunc(`[[[[[[[[[[[]]]]]]]]]]]`, nil))
   114  		t.Run("EncodeRecursive", func(t *testing.T) {
   115  			// add this item to speed up test a bit
   116  			item := NewByteArray(make([]byte, MaxKeySize))
   117  			t.Run("Array", func(t *testing.T) {
   118  				arr := NewArray([]Item{item})
   119  				arr.Append(arr)
   120  				_, err := ToJSON(arr)
   121  				require.Error(t, err)
   122  			})
   123  			t.Run("Map", func(t *testing.T) {
   124  				m := NewMap()
   125  				m.Add(item, m)
   126  				_, err := ToJSON(m)
   127  				require.Error(t, err)
   128  			})
   129  		})
   130  	})
   131  }
   132  
   133  // TestFromJSON_CompatBigInt ensures that maximum BigInt parsing precision matches
   134  // the C# one, ref. https://github.com/neo-project/neo/issues/2879.
   135  func TestFromJSON_CompatBigInt(t *testing.T) {
   136  	tcs := map[string]struct {
   137  		bestPrec   string
   138  		compatPrec string
   139  	}{
   140  		`9.05e+28`: {
   141  			bestPrec:   "90500000000000000000000000000",
   142  			compatPrec: "90499999999999993918259200000",
   143  		},
   144  		`1.871e+21`: {
   145  			bestPrec:   "1871000000000000000000",
   146  			compatPrec: "1871000000000000000000",
   147  		},
   148  		`3.0366e+32`: {
   149  			bestPrec:   "303660000000000000000000000000000",
   150  			compatPrec: "303660000000000004445016810323968",
   151  		},
   152  		`1e+30`: {
   153  			bestPrec:   "1000000000000000000000000000000",
   154  			compatPrec: "1000000000000000019884624838656",
   155  		},
   156  	}
   157  	for in, expected := range tcs {
   158  		t.Run(in, func(t *testing.T) {
   159  			// Best precision.
   160  			actual, err := FromJSON([]byte(in), 5, true)
   161  			require.NoError(t, err)
   162  			require.Equal(t, expected.bestPrec, actual.Value().(*big.Int).String())
   163  
   164  			// Compatible precision.
   165  			actual, err = FromJSON([]byte(in), 5, false)
   166  			require.NoError(t, err)
   167  			require.Equal(t, expected.compatPrec, actual.Value().(*big.Int).String())
   168  		})
   169  	}
   170  }
   171  
   172  func testToJSON(t *testing.T, expectedErr error, item Item) {
   173  	data, err := ToJSON(item)
   174  	if expectedErr != nil {
   175  		require.ErrorIs(t, err, expectedErr)
   176  		return
   177  	}
   178  	require.NoError(t, err)
   179  
   180  	actual, err := FromJSON(data, 1024, true)
   181  	require.NoError(t, err)
   182  	require.Equal(t, item, actual)
   183  }
   184  
   185  func TestToJSONCornerCases(t *testing.T) {
   186  	// base64 encoding increases size by a factor of ~256/64 = 4
   187  	const maxSize = MaxSize / 4
   188  
   189  	bigByteArray := NewByteArray(make([]byte, maxSize/2))
   190  	smallByteArray := NewByteArray(make([]byte, maxSize/4))
   191  	t.Run("Array", func(t *testing.T) {
   192  		arr := NewArray([]Item{bigByteArray})
   193  		testToJSON(t, ErrTooBig, NewArray([]Item{arr, arr}))
   194  
   195  		arr.value[0] = smallByteArray
   196  		testToJSON(t, nil, NewArray([]Item{arr, arr}))
   197  	})
   198  	t.Run("big ByteArray", func(t *testing.T) {
   199  		testToJSON(t, ErrTooBig, NewByteArray(make([]byte, maxSize+4)))
   200  	})
   201  	t.Run("invalid Map key", func(t *testing.T) {
   202  		m := NewMap()
   203  		m.Add(Make([]byte{0xe9}), Make(true))
   204  		testToJSON(t, ErrInvalidValue, m)
   205  	})
   206  }
   207  
   208  // getBigArray returns array takes up a lot of storage when serialized.
   209  func getBigArray(depth int) *Array {
   210  	arr := NewArray([]Item{})
   211  	for i := 0; i < depth; i++ {
   212  		arr = NewArray([]Item{arr, arr})
   213  	}
   214  	return arr
   215  }
   216  
   217  func BenchmarkToJSON(b *testing.B) {
   218  	arr := getBigArray(15)
   219  
   220  	b.ResetTimer()
   221  	b.ReportAllocs()
   222  	for i := 0; i < b.N; i++ {
   223  		_, err := ToJSON(arr)
   224  		if err != nil {
   225  			b.FailNow()
   226  		}
   227  	}
   228  }
   229  
   230  // This test is taken from the C# code
   231  // https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/VM/UT_Helper.cs#L30
   232  func TestToJSONWithTypeCompat(t *testing.T) {
   233  	items := []Item{
   234  		Make(5), Make("hello world"),
   235  		Make([]byte{1, 2, 3}), Make(true),
   236  	}
   237  
   238  	// Note: we use `Equal` and not `JSONEq` because there are no spaces and maps so the order is well-defined.
   239  	s, err := ToJSONWithTypes(items[0])
   240  	assert.NoError(t, err)
   241  	assert.Equal(t, `{"type":"Integer","value":"5"}`, string(s))
   242  
   243  	s, err = ToJSONWithTypes(items[1])
   244  	assert.NoError(t, err)
   245  	assert.Equal(t, `{"type":"ByteString","value":"aGVsbG8gd29ybGQ="}`, string(s))
   246  
   247  	s, err = ToJSONWithTypes(items[2])
   248  	assert.NoError(t, err)
   249  	assert.Equal(t, `{"type":"ByteString","value":"AQID"}`, string(s))
   250  
   251  	s, err = ToJSONWithTypes(items[3])
   252  	assert.NoError(t, err)
   253  	assert.Equal(t, `{"type":"Boolean","value":true}`, string(s))
   254  
   255  	s, err = ToJSONWithTypes(NewArray(items))
   256  	assert.NoError(t, err)
   257  	assert.Equal(t, `{"type":"Array","value":[{"type":"Integer","value":"5"},{"type":"ByteString","value":"aGVsbG8gd29ybGQ="},{"type":"ByteString","value":"AQID"},{"type":"Boolean","value":true}]}`, string(s))
   258  
   259  	item := NewMap()
   260  	item.Add(Make(1), NewPointer(0, []byte{0}))
   261  	s, err = ToJSONWithTypes(item)
   262  	assert.NoError(t, err)
   263  	assert.Equal(t, `{"type":"Map","value":[{"key":{"type":"Integer","value":"1"},"value":{"type":"Pointer","value":0}}]}`, string(s))
   264  }
   265  
   266  func TestToJSONWithTypes(t *testing.T) {
   267  	testCases := []struct {
   268  		name   string
   269  		item   Item
   270  		result string
   271  	}{
   272  		{"Null", Null{}, `{"type":"Any"}`},
   273  		{"Integer", NewBigInteger(big.NewInt(42)), `{"type":"Integer","value":"42"}`},
   274  		{"ByteString", NewByteArray([]byte{1, 2, 3}), `{"type":"ByteString","value":"AQID"}`},
   275  		{"Buffer", NewBuffer([]byte{1, 2, 3}), `{"type":"Buffer","value":"AQID"}`},
   276  		{"BoolTrue", NewBool(true), `{"type":"Boolean","value":true}`},
   277  		{"BoolFalse", NewBool(false), `{"type":"Boolean","value":false}`},
   278  		{"Struct", NewStruct([]Item{Make(11)}),
   279  			`{"type":"Struct","value":[{"type":"Integer","value":"11"}]}`},
   280  		{"Map", NewMapWithValue([]MapElement{{Key: NewBigInteger(big.NewInt(42)), Value: NewBool(false)}}),
   281  			`{"type":"Map","value":[{"key":{"type":"Integer","value":"42"},` +
   282  				`"value":{"type":"Boolean","value":false}}]}`},
   283  		{"Interop", NewInterop(nil),
   284  			`{"type":"InteropInterface"}`},
   285  	}
   286  	for _, tc := range testCases {
   287  		t.Run(tc.name, func(t *testing.T) {
   288  			s, err := ToJSONWithTypes(tc.item)
   289  			require.NoError(t, err)
   290  			require.Equal(t, tc.result, string(s))
   291  
   292  			item, err := FromJSONWithTypes(s)
   293  			require.NoError(t, err)
   294  			require.Equal(t, tc.item, item)
   295  		})
   296  	}
   297  
   298  	t.Run("shared sub struct", func(t *testing.T) {
   299  		t.Run("Buffer", func(t *testing.T) {
   300  			shared := NewBuffer([]byte{1, 2, 3})
   301  			a := NewArray([]Item{shared, shared})
   302  			data, err := ToJSONWithTypes(a)
   303  			require.NoError(t, err)
   304  			expected := `{"type":"Array","value":[` +
   305  				`{"type":"Buffer","value":"AQID"},{"type":"Buffer","value":"AQID"}]}`
   306  			require.Equal(t, expected, string(data))
   307  		})
   308  		t.Run("Array", func(t *testing.T) {
   309  			shared := NewArray([]Item{})
   310  			a := NewArray([]Item{shared, shared})
   311  			data, err := ToJSONWithTypes(a)
   312  			require.NoError(t, err)
   313  			expected := `{"type":"Array","value":[` +
   314  				`{"type":"Array","value":[]},{"type":"Array","value":[]}]}`
   315  			require.Equal(t, expected, string(data))
   316  		})
   317  		t.Run("Map", func(t *testing.T) {
   318  			shared := NewMap()
   319  			m := NewMapWithValue([]MapElement{
   320  				{NewBool(true), shared},
   321  				{NewBool(false), shared},
   322  			})
   323  			data, err := ToJSONWithTypes(m)
   324  			require.NoError(t, err)
   325  			expected := `{"type":"Map","value":[` +
   326  				`{"key":{"type":"Boolean","value":true},"value":{"type":"Map","value":[]}},` +
   327  				`{"key":{"type":"Boolean","value":false},"value":{"type":"Map","value":[]}}]}`
   328  			require.Equal(t, expected, string(data))
   329  		})
   330  	})
   331  
   332  	t.Run("Invalid", func(t *testing.T) {
   333  		t.Run("RecursiveArray", func(t *testing.T) {
   334  			arr := NewArray(nil)
   335  			arr.value = []Item{Make(5), arr, Make(true)}
   336  
   337  			_, err := ToJSONWithTypes(arr)
   338  			require.Error(t, err)
   339  		})
   340  		t.Run("RecursiveMap", func(t *testing.T) {
   341  			m := NewMap()
   342  			m.Add(Make(3), Make(true))
   343  			m.Add(Make(5), m)
   344  
   345  			_, err := ToJSONWithTypes(m)
   346  			require.Error(t, err)
   347  		})
   348  	})
   349  }
   350  
   351  func TestToJSONWithTypesBadCases(t *testing.T) {
   352  	bigBuf := make([]byte, MaxSize)
   353  
   354  	t.Run("issue 2385", func(t *testing.T) {
   355  		const maxStackSize = 2 * 1024
   356  
   357  		items := make([]Item, maxStackSize)
   358  		for i := range items {
   359  			items[i] = NewBuffer(bigBuf)
   360  		}
   361  		_, err := ToJSONWithTypes(NewArray(items))
   362  		require.ErrorIs(t, err, errTooBigSize)
   363  	})
   364  	t.Run("overflow on primitive item", func(t *testing.T) {
   365  		_, err := ToJSONWithTypes(NewBuffer(bigBuf))
   366  		require.ErrorIs(t, err, errTooBigSize)
   367  	})
   368  	t.Run("overflow on array element", func(t *testing.T) {
   369  		b := NewBuffer(bigBuf[:MaxSize/2])
   370  		_, err := ToJSONWithTypes(NewArray([]Item{b, b}))
   371  		require.ErrorIs(t, err, errTooBigSize)
   372  	})
   373  	t.Run("overflow on map key", func(t *testing.T) {
   374  		m := NewMapWithValue([]MapElement{
   375  			{NewBool(true), NewBool(true)},
   376  			{NewByteArray(bigBuf), NewBool(true)},
   377  		})
   378  		_, err := ToJSONWithTypes(m)
   379  		require.ErrorIs(t, err, errTooBigSize)
   380  	})
   381  	t.Run("overflow on the last byte of array", func(t *testing.T) {
   382  		// Construct big enough buffer and pad with integer digits
   383  		// until the necessary branch is covered #ididthemath.
   384  		arr := NewArray([]Item{
   385  			NewByteArray(bigBuf[:MaxSize/4*3-70]),
   386  			NewBigInteger(big.NewInt(123456)),
   387  		})
   388  		_, err := ToJSONWithTypes(arr)
   389  		require.ErrorIs(t, err, errTooBigSize)
   390  	})
   391  	t.Run("overflow on the item prefix", func(t *testing.T) {
   392  		arr := NewArray([]Item{
   393  			NewByteArray(bigBuf[:MaxSize/4*3-60]),
   394  			NewBool(true),
   395  		})
   396  		_, err := ToJSONWithTypes(arr)
   397  		require.ErrorIs(t, err, errTooBigSize)
   398  	})
   399  	t.Run("overflow on null", func(t *testing.T) {
   400  		arr := NewArray([]Item{
   401  			NewByteArray(bigBuf[:MaxSize/4*3-52]),
   402  			Null{},
   403  		})
   404  		_, err := ToJSONWithTypes(arr)
   405  		require.ErrorIs(t, err, errTooBigSize)
   406  	})
   407  	t.Run("overflow on interop", func(t *testing.T) {
   408  		arr := NewArray([]Item{
   409  			NewByteArray(bigBuf[:MaxSize/4*3-52]),
   410  			NewInterop(42),
   411  		})
   412  		_, err := ToJSONWithTypes(arr)
   413  		require.ErrorIs(t, err, errTooBigSize)
   414  	})
   415  	t.Run("overflow on cached item", func(t *testing.T) {
   416  		b := NewArray([]Item{NewByteArray(bigBuf[:MaxSize/2])})
   417  		arr := NewArray([]Item{b, b})
   418  		_, err := ToJSONWithTypes(arr)
   419  		require.ErrorIs(t, err, errTooBigSize)
   420  	})
   421  	t.Run("invalid type", func(t *testing.T) {
   422  		_, err := ToJSONWithTypes(nil)
   423  		require.ErrorIs(t, err, ErrUnserializable)
   424  	})
   425  }
   426  
   427  func TestFromJSONWithTypes(t *testing.T) {
   428  	testCases := []struct {
   429  		name string
   430  		json string
   431  		item Item
   432  	}{
   433  		{"Pointer", `{"type":"Pointer","value":3}`, NewPointer(3, nil)},
   434  		{"Interop", `{"type":"InteropInterface"}`, NewInterop(nil)},
   435  		{"Null", `{"type":"Any"}`, Null{}},
   436  		{"Array", `{"type":"Array","value":[{"type":"Any"}]}`, NewArray([]Item{Null{}})},
   437  	}
   438  	for _, tc := range testCases {
   439  		t.Run(tc.name, func(t *testing.T) {
   440  			item, err := FromJSONWithTypes([]byte(tc.json))
   441  			require.NoError(t, err)
   442  			require.Equal(t, tc.item, item)
   443  		})
   444  	}
   445  
   446  	t.Run("Invalid", func(t *testing.T) {
   447  		errCases := []struct {
   448  			name string
   449  			json string
   450  		}{
   451  			{"InvalidType", `{"type":int,"value":"4"`},
   452  			{"UnexpectedType", `{"type":"int","value":"4"}`},
   453  			{"IntegerValue1", `{"type":"Integer","value": 4}`},
   454  			{"IntegerValue2", `{"type":"Integer","value": "a"}`},
   455  			{"BoolValue", `{"type":"Boolean","value": "str"}`},
   456  			{"PointerValue", `{"type":"Pointer","value": "str"}`},
   457  			{"BufferValue1", `{"type":"Buffer","value":"not a base 64"}`},
   458  			{"BufferValue2", `{"type":"Buffer","value":123}`},
   459  			{"ArrayValue", `{"type":"Array","value":3}`},
   460  			{"ArrayElement", `{"type":"Array","value":[3]}`},
   461  			{"MapValue", `{"type":"Map","value":3}`},
   462  			{"MapElement", `{"type":"Map","value":[{"key":"value"}]}`},
   463  			{"MapElementKeyNotPrimitive", `{"type":"Map","value":[{"key":{"type":"Any"}}]}`},
   464  			{"MapElementValue", `{"type":"Map","value":[` +
   465  				`{"key":{"type":"Integer","value":"3"},"value":3}]}`},
   466  		}
   467  		for _, tc := range errCases {
   468  			t.Run(tc.name, func(t *testing.T) {
   469  				_, err := FromJSONWithTypes([]byte(tc.json))
   470  				require.Error(t, err)
   471  			})
   472  		}
   473  	})
   474  }