Skip to content

Commit

Permalink
vm: move JNumbers parsing precision under HFBasilisk
Browse files Browse the repository at this point in the history
Signed-off-by: Anna Shaleva <[email protected]>
  • Loading branch information
AnnaShaleva committed Aug 8, 2023
1 parent deb1c45 commit a24fc12
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 23 deletions.
5 changes: 3 additions & 2 deletions pkg/core/native/std.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
Expand Down Expand Up @@ -199,13 +200,13 @@ func (s *Std) jsonSerialize(_ *interop.Context, args []stackitem.Item) stackitem
return stackitem.NewByteArray(data)
}

func (s *Std) jsonDeserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item {
func (s *Std) jsonDeserialize(ic *interop.Context, args []stackitem.Item) stackitem.Item {
data, err := args[0].TryBytes()
if err != nil {
panic(err)
}

item, err := stackitem.FromJSON(data, stackitem.MaxDeserialized)
item, err := stackitem.FromJSON(data, stackitem.MaxDeserialized, ic.IsHardforkEnabled(config.HFBasilisk))
if err != nil {
panic(err)
}
Expand Down
31 changes: 23 additions & 8 deletions pkg/vm/stackitem/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type decoder struct {

count int
depth int
// bestIntPrecision denotes whether maximum allowed integer precision should
// be used to parse big.Int items. If false, then default NeoC# value will be
// used which doesn't allow to precisely parse big values.
bestIntPrecision bool
}

// MaxAllowedInteger is the maximum integer allowed to be encoded.
Expand All @@ -26,10 +30,16 @@ const MaxAllowedInteger = 2<<53 - 1
// MaxJSONDepth is the maximum allowed nesting level of an encoded/decoded JSON.
const MaxJSONDepth = 10

// MaxIntegerPrec is the maximum precision allowed for big.Integer parsing.
// It allows to properly parse integer numbers that our 256-bit VM is able to
// handle.
const MaxIntegerPrec = 1<<8 + 1
const (
// MaxIntegerPrec is the maximum precision allowed for big.Integer parsing.
// It allows to properly parse integer numbers that our 256-bit VM is able to
// handle.
MaxIntegerPrec = 1<<8 + 1
// CompatIntegerPrec is the maximum precision allowed for big.Integer parsing
// by the C# node before the Basilisk hardfork. It doesn't allow to precisely
// parse big numbers, see the https://github.com/neo-project/neo/issues/2879.
CompatIntegerPrec = 53
)

// ErrInvalidValue is returned when an item value doesn't fit some constraints
// during serialization or deserialization.
Expand Down Expand Up @@ -166,10 +176,11 @@ func itemToJSONString(it Item) ([]byte, error) {
// null -> Null
// array -> Array
// map -> Map, keys are UTF-8
func FromJSON(data []byte, maxCount int) (Item, error) {
func FromJSON(data []byte, maxCount int, bestIntPrecision bool) (Item, error) {
d := decoder{
Decoder: *json.NewDecoder(bytes.NewReader(data)),
count: maxCount,
Decoder: *json.NewDecoder(bytes.NewReader(data)),
count: maxCount,
bestIntPrecision: bestIntPrecision,
}
d.UseNumber()
if item, err := d.decode(); err != nil {
Expand Down Expand Up @@ -226,7 +237,11 @@ func (d *decoder) decode() (Item, error) {
if isScientific {
// As a special case numbers like 2.8e+22 are allowed (SetString rejects them).
// That's the way how C# code works.
f, _, err := big.ParseFloat(ts, 10, MaxIntegerPrec, big.ToNearestEven)
var prec uint = CompatIntegerPrec
if d.bestIntPrecision {
prec = MaxIntegerPrec
}
f, _, err := big.ParseFloat(ts, 10, prec, big.ToNearestEven)
if err != nil {
return nil, fmt.Errorf("%w (malformed exp value for int)", ErrInvalidValue)
}
Expand Down
47 changes: 34 additions & 13 deletions pkg/vm/stackitem/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func getTestDecodeFunc(js string, expected ...interface{}) func(t *testing.T) {

func getTestDecodeEncodeFunc(js string, needEncode bool, expected ...interface{}) func(t *testing.T) {
return func(t *testing.T) {
actual, err := FromJSON([]byte(js), 20)
actual, err := FromJSON([]byte(js), 20, true)
if expected[0] == nil {
require.Error(t, err)
return
Expand Down Expand Up @@ -59,10 +59,10 @@ func TestFromToJSON(t *testing.T) {
NewArray([]Item{NewArray([]Item{}), NewArray([]Item{NewMap(), Null{}})})))
t.Run("ManyElements", func(t *testing.T) {
js := `[1, 2, 3]` // 3 elements + array itself
_, err := FromJSON([]byte(js), 4)
_, err := FromJSON([]byte(js), 4, true)
require.NoError(t, err)

_, err = FromJSON([]byte(js), 3)
_, err = FromJSON([]byte(js), 3, true)
require.ErrorIs(t, err, errTooBigElements)
})
})
Expand All @@ -82,10 +82,10 @@ func TestFromToJSON(t *testing.T) {

t.Run("ManyElements", func(t *testing.T) {
js := `{"a":1,"b":3}` // 4 elements + map itself
_, err := FromJSON([]byte(js), 5)
_, err := FromJSON([]byte(js), 5, true)
require.NoError(t, err)

_, err = FromJSON([]byte(js), 4)
_, err = FromJSON([]byte(js), 4, true)
require.ErrorIs(t, err, errTooBigElements)
})
})
Expand Down Expand Up @@ -133,17 +133,38 @@ func TestFromToJSON(t *testing.T) {
// TestFromJSON_CompatBigInt ensures that maximum BigInt parsing precision matches
// the C# one, ref. https://github.com/neo-project/neo/issues/2879.
func TestFromJSON_CompatBigInt(t *testing.T) {
tcs := map[string]string{
`9.05e+28`: "90500000000000000000000000000",
`1.871e+21`: "1871000000000000000000",
`3.0366e+32`: "303660000000000000000000000000000",
`1e+30`: "1000000000000000000000000000000",
tcs := map[string]struct {
bestPrec string
compatPrec string
}{
`9.05e+28`: {
bestPrec: "90500000000000000000000000000",
compatPrec: "90499999999999993918259200000",
},
`1.871e+21`: {
bestPrec: "1871000000000000000000",
compatPrec: "1871000000000000000000",
},
`3.0366e+32`: {
bestPrec: "303660000000000000000000000000000",
compatPrec: "303660000000000004445016810323968",
},
`1e+30`: {
bestPrec: "1000000000000000000000000000000",
compatPrec: "1000000000000000019884624838656",
},
}
for in, expected := range tcs {
t.Run(in, func(t *testing.T) {
actual, err := FromJSON([]byte(in), 5)
// Best precision.
actual, err := FromJSON([]byte(in), 5, true)
require.NoError(t, err)
require.Equal(t, expected.bestPrec, actual.Value().(*big.Int).String())

// Compatible precision.
actual, err = FromJSON([]byte(in), 5, false)
require.NoError(t, err)
require.Equal(t, expected, actual.Value().(*big.Int).String())
require.Equal(t, expected.compatPrec, actual.Value().(*big.Int).String())
})
}
}
Expand All @@ -156,7 +177,7 @@ func testToJSON(t *testing.T, expectedErr error, item Item) {
}
require.NoError(t, err)

actual, err := FromJSON(data, 1024)
actual, err := FromJSON(data, 1024, true)
require.NoError(t, err)
require.Equal(t, item, actual)
}
Expand Down

0 comments on commit a24fc12

Please sign in to comment.