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 }