diff --git a/src/Neo.Json/JNumber.cs b/src/Neo.Json/JNumber.cs index 5b142feb41..0842ed92e4 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 @@ -38,9 +39,20 @@ public class JNumber : JToken /// Initializes a new instance of the class with the specified value. /// /// The value of the JSON token. - public JNumber(double value = 0) + /// True if we want to ensure that the value is in the limits. + public JNumber(double value = 0, bool checkMinMax = true) { - if (!double.IsFinite(value)) throw new FormatException(); + if (checkMinMax) + { + 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)); + } + + if (!double.IsFinite(value)) + throw new ArgumentException("value is not finite", nameof(value)); + Value = value; } @@ -125,6 +137,22 @@ public static implicit operator JNumber(long value) return new JNumber(value); } + public static implicit operator JNumber(BigInteger 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 a01beba974..b4dbf38ca3 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; @@ -295,10 +296,15 @@ public static implicit operator JToken(JToken?[] value) public static implicit operator JToken(bool value) { - return (JBoolean)value; + return new JBoolean(value); } public static implicit operator JToken(double value) + { + return new JNumber(value); + } + + public static implicit operator JToken(BigInteger value) { return (JNumber)value; } diff --git a/src/Neo/SmartContract/JsonSerializer.cs b/src/Neo/SmartContract/JsonSerializer.cs index d2055be7d8..482adefc54 100644 --- a/src/Neo/SmartContract/JsonSerializer.cs +++ b/src/Neo/SmartContract/JsonSerializer.cs @@ -35,14 +35,15 @@ public static class JsonSerializer /// Serializes a to a . /// /// The to serialize. + /// Hardfork checker /// The serialized object. - public static JToken Serialize(StackItem item) + public static JToken Serialize(StackItem item, Func hardforkChecker = null) { switch (item) { case Array array: { - return array.Select(p => Serialize(p)).ToArray(); + return array.Select(p => Serialize(p, hardforkChecker)).ToArray(); } case ByteString _: case Buffer _: @@ -51,10 +52,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 new JNumber((long)num.GetInteger(), hardforkChecker == null || hardforkChecker(Hardfork.HF_Echidna)); } case Boolean boolean: { @@ -66,10 +64,10 @@ 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); + var value = Serialize(entry.Value, hardforkChecker); ret[key] = value; } diff --git a/tests/Neo.Json.UnitTests/UT_JNumber.cs b/tests/Neo.Json.UnitTests/UT_JNumber.cs index df8bdca619..082598c126 100644 --- a/tests/Neo.Json.UnitTests/UT_JNumber.cs +++ b/tests/Neo.Json.UnitTests/UT_JNumber.cs @@ -39,20 +39,47 @@ 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); + ((JNumber)JNumber.MAX_SAFE_INTEGER).AsNumber().Should().Be(JNumber.MAX_SAFE_INTEGER); + ((JNumber)JNumber.MIN_SAFE_INTEGER).AsNumber().Should().Be(JNumber.MIN_SAFE_INTEGER); + } + + [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 fc29c95c12..2cbed61293 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, null, ProtocolSettings.Default); - 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]