diff --git a/src/Neo.Json/JNumber.cs b/src/Neo.Json/JNumber.cs index 479303cfb5..95b1f08ef2 100644 --- a/src/Neo.Json/JNumber.cs +++ b/src/Neo.Json/JNumber.cs @@ -10,6 +10,7 @@ // modifications are permitted. using System.Globalization; +using System.Numerics; using System.Text.Json; namespace Neo.Json @@ -40,7 +41,13 @@ public class JNumber : JToken /// The value of the JSON token. public JNumber(double value = 0) { - if (!double.IsFinite(value)) throw new FormatException(); + if (!double.IsFinite(value)) + throw new ArgumentException("value is not finite", nameof(value)); + if (value > MAX_SAFE_INTEGER) + throw new ArgumentException("value is higher than MAX_SAFE_INTEGER", nameof(value)); + if (value < MIN_SAFE_INTEGER) + throw new ArgumentException("value is lower than MIN_SAFE_INTEGER", nameof(value)); + this.Value = value; } @@ -119,11 +126,22 @@ public static implicit operator JNumber(double value) return new JNumber(value); } - public static implicit operator JNumber(long value) + public static implicit operator JNumber(BigInteger value) { - return new JNumber(value); + return new JNumber((long)value); } + /// + /// Check if two JNumber are equal. + /// + /// Non null value + /// Nullable value + /// bool + /// + /// If the left is null, throw an . + /// If the right is null, return false. + /// If the left and right are the same object, return true. + /// public static bool operator ==(JNumber left, JNumber? right) { if (right is null) return false; diff --git a/src/Neo.Json/JToken.cs b/src/Neo.Json/JToken.cs index bb6a510a69..4316bfd27e 100644 --- a/src/Neo.Json/JToken.cs +++ b/src/Neo.Json/JToken.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System.Numerics; using System.Text.Json; using static Neo.Json.Utility; @@ -298,6 +299,11 @@ public static implicit operator JToken(double value) return (JNumber)value; } + public static implicit operator JToken(BigInteger value) + { + return (JNumber)value; + } + public static implicit operator JToken?(string? value) { return (JString?)value; diff --git a/src/Neo/SmartContract/JsonSerializer.cs b/src/Neo/SmartContract/JsonSerializer.cs index 4d77104a6e..86ae75748c 100644 --- a/src/Neo/SmartContract/JsonSerializer.cs +++ b/src/Neo/SmartContract/JsonSerializer.cs @@ -51,10 +51,7 @@ public static JToken Serialize(StackItem item) } case Integer num: { - var integer = num.GetInteger(); - if (integer > JNumber.MAX_SAFE_INTEGER || integer < JNumber.MIN_SAFE_INTEGER) - throw new InvalidOperationException(); - return (double)integer; + return num.GetInteger(); } case Boolean boolean: { @@ -66,7 +63,7 @@ public static JToken Serialize(StackItem item) foreach (var entry in map) { - if (!(entry.Key is ByteString)) throw new FormatException(); + if (entry.Key is not ByteString) throw new FormatException(); var key = entry.Key.GetString(); var value = Serialize(entry.Value); diff --git a/tests/Neo.Json.UnitTests/UT_JNumber.cs b/tests/Neo.Json.UnitTests/UT_JNumber.cs index 6eb0598fd3..4342c46451 100644 --- a/tests/Neo.Json.UnitTests/UT_JNumber.cs +++ b/tests/Neo.Json.UnitTests/UT_JNumber.cs @@ -9,6 +9,8 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System.Numerics; + namespace Neo.Json.UnitTests { enum Woo @@ -37,20 +39,45 @@ public void SetUp() public void TestAsBoolean() { maxInt.AsBoolean().Should().BeTrue(); + minInt.AsBoolean().Should().BeTrue(); zero.AsBoolean().Should().BeFalse(); } + [TestMethod] + public void TestBigInteger() + { + ((JNumber)BigInteger.One).AsNumber().Should().Be(1); + ((JNumber)BigInteger.Zero).AsNumber().Should().Be(0); + ((JNumber)BigInteger.MinusOne).AsNumber().Should().Be(-1); + } + + [TestMethod] + public void TestNullEqual() + { + JNumber nullJNumber = null; + + Assert.IsFalse(maxInt.Equals(null)); + Assert.IsFalse(maxInt == null); + Assert.IsFalse(minInt.Equals(null)); + Assert.IsFalse(minInt == null); + Assert.IsFalse(zero == null); + + Assert.ThrowsException(() => nullJNumber == maxInt); + Assert.ThrowsException(() => nullJNumber == minInt); + Assert.ThrowsException(() => nullJNumber == zero); + } + [TestMethod] public void TestAsString() { Action action1 = () => new JNumber(double.PositiveInfinity).AsString(); - action1.Should().Throw(); + action1.Should().Throw(); Action action2 = () => new JNumber(double.NegativeInfinity).AsString(); - action2.Should().Throw(); + action2.Should().Throw(); Action action3 = () => new JNumber(double.NaN).AsString(); - action3.Should().Throw(); + action3.Should().Throw(); } [TestMethod] diff --git a/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs b/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs index 3ab32ad7a5..675727757f 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs @@ -43,7 +43,7 @@ public void JsonTest_WrongJson() Assert.ThrowsException(() => JObject.Parse(json)); json = @"{""length"":99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999}"; - Assert.ThrowsException(() => JObject.Parse(json)); + Assert.ThrowsException(() => JObject.Parse(json)); } [TestMethod] @@ -91,10 +91,10 @@ public void JsonTest_Numbers() Assert.AreEqual("[1,-2,3.5]", parsed.ToString()); - json = "[200.500000E+005,200.500000e+5,-1.1234e-100,9.05E+28]"; + json = "[200.500000E+005,200.500000e+5,-1.1234e-100,9.05E+8]"; parsed = JObject.Parse(json); - Assert.AreEqual("[20050000,20050000,-1.1234E-100,9.05E+28]", parsed.ToString()); + Assert.AreEqual("[20050000,20050000,-1.1234E-100,905000000]", parsed.ToString()); json = "[-]"; Assert.ThrowsException(() => JObject.Parse(json)); @@ -216,7 +216,7 @@ public void Serialize_EmptyObject() public void Serialize_Number() { var entry = new VM.Types.Array { 1, 9007199254740992 }; - Assert.ThrowsException(() => JsonSerializer.Serialize(entry)); + Assert.ThrowsException(() => JsonSerializer.Serialize(entry)); } [TestMethod] @@ -303,7 +303,7 @@ public void Serialize_Array_Bool_Str_Num() public void Deserialize_Array_Bool_Str_Num() { ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); - var items = JsonSerializer.Deserialize(engine, JObject.Parse("[true,\"test\",123,9.05E+28]"), ExecutionEngineLimits.Default); + var items = JsonSerializer.Deserialize(engine, JObject.Parse("[true,\"test\",123,1.05E+4]"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); Assert.AreEqual(((VM.Types.Array)items).Count, 4); @@ -313,7 +313,7 @@ public void Deserialize_Array_Bool_Str_Num() Assert.IsTrue(array[0].GetBoolean()); Assert.AreEqual(array[1].GetString(), "test"); Assert.AreEqual(array[2].GetInteger(), 123); - Assert.AreEqual(array[3].GetInteger(), BigInteger.Parse("90500000000000000000000000000")); + Assert.AreEqual(array[3].GetInteger(), BigInteger.Parse("10500")); } [TestMethod]