From c68b73e8790f6e2ec15fc9ec0109fd6bf43cb1de Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 3 Mar 2024 21:12:35 +0800 Subject: [PATCH] [Compiler Add]allow direct assignment to UInt160, UInt256, and ECPoint. (#974) --- .../TransferContract.cs | 4 +- .../MethodConvert/Expression/Expression.cs | 54 ++++++++++++++++- .../MethodConvert/MethodConvert.cs | 2 +- src/Neo.SmartContract.Framework/ECPoint.cs | 25 ++++++-- src/Neo.SmartContract.Framework/UInt160.cs | 25 ++++++-- src/Neo.SmartContract.Framework/UInt256.cs | 24 ++++++-- .../Contract_DirectInit.cs | 59 +++++++++++++++++++ .../Contract_StaticVar.cs | 17 ++++-- .../Contract_StaticVarInit.cs | 2 +- .../Contract_TryCatch.cs | 4 +- .../UnitTest_DirectInit.cs | 55 +++++++++++++++++ .../UnitTest_StaticVar.cs | 11 ++++ 12 files changed, 252 insertions(+), 30 deletions(-) create mode 100644 tests/Neo.Compiler.CSharp.TestContracts/Contract_DirectInit.cs create mode 100644 tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DirectInit.cs diff --git a/examples/Example.SmartContract.Transfer/TransferContract.cs b/examples/Example.SmartContract.Transfer/TransferContract.cs index 237aafa93..2d72165b1 100644 --- a/examples/Example.SmartContract.Transfer/TransferContract.cs +++ b/examples/Example.SmartContract.Transfer/TransferContract.cs @@ -31,8 +31,8 @@ namespace Transfer; [ContractPermission(Permission.WildCard, Method.WildCard)] public class TransferContract : SmartContract { - [Hash160("NUuJw4C4XJFzxAvSZnFTfsNoWZytmQKXQP")] - private static readonly UInt160 Owner = default; + // [Hash160("NUuJw4C4XJFzxAvSZnFTfsNoWZytmQKXQP")] + private static readonly UInt160 Owner = "NUuJw4C4XJFzxAvSZnFTfsNoWZytmQKXQP"; /// /// Transfer method that demonstrate how to transfer NEO and GAS diff --git a/src/Neo.Compiler.CSharp/MethodConvert/Expression/Expression.cs b/src/Neo.Compiler.CSharp/MethodConvert/Expression/Expression.cs index 89ae863de..b317ddf7c 100644 --- a/src/Neo.Compiler.CSharp/MethodConvert/Expression/Expression.cs +++ b/src/Neo.Compiler.CSharp/MethodConvert/Expression/Expression.cs @@ -15,19 +15,67 @@ using Neo.SmartContract.Native; using Neo.VM; using System.Numerics; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Wallets; namespace Neo.Compiler; partial class MethodConvert { - private void ConvertExpression(SemanticModel model, ExpressionSyntax syntax) + private void ConvertExpression(SemanticModel model, ExpressionSyntax syntax, SyntaxNode? syntaxNode = null) { using var sequence = InsertSequencePoint(syntax); Optional constant = model.GetConstantValue(syntax); if (constant.HasValue) { - Push(constant.Value); + var value = constant.Value; + + ITypeSymbol? typeSymbol = null; + if (syntaxNode is VariableDeclaratorSyntax variableDeclarator) + { + var declaration = variableDeclarator.Parent as VariableDeclarationSyntax; + if (declaration != null) + { + typeSymbol = ModelExtensions.GetTypeInfo(model, declaration.Type).Type; + } + } + else if (syntaxNode is PropertyDeclarationSyntax propertyDeclaration) + { + typeSymbol = ModelExtensions.GetTypeInfo(model, propertyDeclaration.Type).Type; + } + + if (typeSymbol != null) + { + string fullName = typeSymbol.ToDisplayString(); + + switch (fullName) + { + case "Neo.SmartContract.Framework.UInt160": + var strValue = (string)value; + value = (UInt160.TryParse(strValue, out var hash) + ? hash + : strValue.ToScriptHash(_context.Options.AddressVersion)).ToArray(); + break; + case "Neo.SmartContract.Framework.UInt256": + strValue = (string)value; + value = strValue.HexToBytes(true); + if (((byte[])value).Length != 32) + throw new CompilationException(syntax, DiagnosticId.InvalidInitialValue, "Invalid UInt256 literal"); + break; + case "Neo.SmartContract.Framework.ECPoint": + strValue = (string)value; + value = ECPoint.Parse(strValue, ECCurve.Secp256r1).EncodePoint(true); + break; + case "Neo.SmartContract.Framework.ByteArray": + strValue = (string)value; + value = strValue.HexToBytes(true); + break; + } + } + + Push(value); return; } @@ -165,7 +213,7 @@ private void EnsureIntegerInRange(ITypeSymbol type) private void ConvertObjectToString(SemanticModel model, ExpressionSyntax expression) { - ITypeSymbol? type = model.GetTypeInfo(expression).Type; + ITypeSymbol? type = ModelExtensions.GetTypeInfo(model, expression).Type; switch (type?.ToString()) { case "sbyte": diff --git a/src/Neo.Compiler.CSharp/MethodConvert/MethodConvert.cs b/src/Neo.Compiler.CSharp/MethodConvert/MethodConvert.cs index ae1fea81b..20ff4c58f 100644 --- a/src/Neo.Compiler.CSharp/MethodConvert/MethodConvert.cs +++ b/src/Neo.Compiler.CSharp/MethodConvert/MethodConvert.cs @@ -279,7 +279,7 @@ private void ProcessFieldInitializer(SemanticModel model, IFieldSymbol field, Ac using (InsertSequencePoint(syntaxNode)) { preInitialize?.Invoke(); - ConvertExpression(model, initializer.Value); + ConvertExpression(model, initializer.Value, syntaxNode); postInitialize?.Invoke(); } } diff --git a/src/Neo.SmartContract.Framework/ECPoint.cs b/src/Neo.SmartContract.Framework/ECPoint.cs index d622498ff..b71004d5f 100644 --- a/src/Neo.SmartContract.Framework/ECPoint.cs +++ b/src/Neo.SmartContract.Framework/ECPoint.cs @@ -1,13 +1,14 @@ // Copyright (C) 2015-2023 The Neo Project. -// -// The Neo.SmartContract.Framework is free software distributed under the MIT -// software license, see the accompanying file LICENSE in the main directory -// of the project or http://www.opensource.org/licenses/mit-license.php +// +// The Neo.SmartContract.Framework is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System; using Neo.SmartContract.Framework.Attributes; namespace Neo.SmartContract.Framework @@ -39,5 +40,19 @@ public extern bool IsValid [OpCode(OpCode.CONVERT, StackItemType.Buffer)] public static extern explicit operator byte[](ECPoint value); + + /// + /// Implicitly converts a hexadecimal string to a PublicKey object. + /// Assumes the string is a valid hexadecimal representation. + /// + /// + /// PublicKey from a 33 bytes (66 characters) hexadecimal string: + /// "024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9" + /// + /// + /// This is a compile time conversion, only work with constant string. + /// If you want to convert a runtime string, convert it to byte[] first. + /// + public static extern implicit operator ECPoint(string value); } } diff --git a/src/Neo.SmartContract.Framework/UInt160.cs b/src/Neo.SmartContract.Framework/UInt160.cs index c62ff090f..69b26a2f3 100644 --- a/src/Neo.SmartContract.Framework/UInt160.cs +++ b/src/Neo.SmartContract.Framework/UInt160.cs @@ -1,13 +1,14 @@ // Copyright (C) 2015-2023 The Neo Project. -// -// The Neo.SmartContract.Framework is free software distributed under the MIT -// software license, see the accompanying file LICENSE in the main directory -// of the project or http://www.opensource.org/licenses/mit-license.php +// +// The Neo.SmartContract.Framework is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System; using Neo.SmartContract.Framework.Attributes; using Neo.SmartContract.Framework.Native; using Neo.SmartContract.Framework.Services; @@ -71,5 +72,19 @@ public string ToAddress(byte version) data = Helper.Concat(data, this); return StdLib.Base58CheckEncode((ByteString)data); } + + /// + /// Implicitly converts a hexadecimal string to a UInt160 object. + /// This can be a 20 bytes hex string or a neo address. + /// + /// 20 bytes hex string: "01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4" (no prefix) + /// Address: "NZNosnRn6FpRjwGKx8VdXv5Sn7BvzrjZVb" + /// + /// + /// This is a compile time conversion, only work with constant string. + /// If you want to convert a runtime string, convert it to byte[] first. + /// + /// + public static extern implicit operator UInt160(string value); } } diff --git a/src/Neo.SmartContract.Framework/UInt256.cs b/src/Neo.SmartContract.Framework/UInt256.cs index db61ade04..a1762ec3b 100644 --- a/src/Neo.SmartContract.Framework/UInt256.cs +++ b/src/Neo.SmartContract.Framework/UInt256.cs @@ -1,10 +1,10 @@ // Copyright (C) 2015-2023 The Neo Project. -// -// The Neo.SmartContract.Framework is free software distributed under the MIT -// software license, see the accompanying file LICENSE in the main directory -// of the project or http://www.opensource.org/licenses/mit-license.php +// +// The Neo.SmartContract.Framework is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -48,5 +48,19 @@ public extern bool IsValid [OpCode(OpCode.CONVERT, StackItemType.Buffer)] public static extern explicit operator byte[](UInt256 value); + + /// + /// Implicitly converts a hexadecimal string to a UInt256 object. + /// Assumes the string is a valid hexadecimal representation. + /// + /// 32 bytes (64 characters) hexadecimal string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" (no prefix) + /// + /// The input `MUST` be a valid hex string of a 32 bytes data. + /// + /// This is a compile time conversion, only work with constant string. + /// If you want to convert a runtime string, convert it to byte[] first. + /// + /// + public static extern implicit operator UInt256(string value); } } diff --git a/tests/Neo.Compiler.CSharp.TestContracts/Contract_DirectInit.cs b/tests/Neo.Compiler.CSharp.TestContracts/Contract_DirectInit.cs new file mode 100644 index 000000000..b372d76c6 --- /dev/null +++ b/tests/Neo.Compiler.CSharp.TestContracts/Contract_DirectInit.cs @@ -0,0 +1,59 @@ +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; +using System.Numerics; + +namespace Neo.Compiler.CSharp.UnitTests.TestClasses +{ + + public class Contract_DirectInit : SmartContract.Framework.SmartContract + { + + /// + /// A static field of type ECPoint initialized directly from a string. This is used to demonstrate initializing + /// complex types like ECPoint at compile time to avoid runtime overhead. + /// + // [PublicKey("024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9")] + private static readonly ECPoint eCPoint = "024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9"; + + /// + /// A static field of type UInt160 initialized directly from a string. This allows for compile-time + /// initialization of blockchain-specific types like addresses, represented here as Hash160. + /// + // [Hash160("NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq")] + private static readonly UInt160 uInt160 = "NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq"; + + /// + /// A static field of type UInt160 initialized directly from a hex string. This allows for compile-time + /// initialization of blockchain-specific types like addresses, represented here as Hash256. + /// + // [ByteArray("edcf8679104ec2911a4fe29ad7db232a493e5b990fb1da7af0c7b989948c8925")] + private static readonly UInt256 validUInt256 = "edcf8679104ec2911a4fe29ad7db232a493e5b990fb1da7af0c7b989948c8925"; + + /// + /// A static string field initialized directly. + /// This demonstrates initializing contract fields that cannot be directly assigned with their value at compile time. + /// + // [String("hello world")] + public static readonly string a4 = "hello world"; + + public static UInt160 testGetUInt160() + { + return uInt160; + } + + public static ECPoint testGetECPoint() + { + return eCPoint; + } + + public static UInt256 testGetUInt256() + { + return validUInt256; + } + + public static string testGetString() + { + return a4; + } + } +} diff --git a/tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVar.cs b/tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVar.cs index 3bf7ff377..0bce7f160 100644 --- a/tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVar.cs +++ b/tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVar.cs @@ -24,21 +24,21 @@ public class Contract_StaticVar : SmartContract.Framework.SmartContract /// A static field of type ECPoint initialized with the InitialValue attribute. This is used to demonstrate initializing /// complex types like ECPoint at compile time to avoid runtime overhead. /// - [PublicKey("024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9")] - private static readonly ECPoint eCPoint = default; + // [PublicKey("024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9")] + private static readonly ECPoint eCPoint = "024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9"; /// /// A static field of type UInt160 initialized with the InitialValue attribute. This allows for compile-time /// initialization of blockchain-specific types like addresses, represented here as Hash160. /// - [Hash160("NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq")] - private static readonly UInt160 uInt160 = default; + // [Hash160("NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq")] + private static readonly UInt160 uInt160 = "NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq"; /// /// A static string field initialized with the InitialValue attribute. This demonstrates initializing contract fields that cannot be directly assigned with their value at compile time. /// - [String("hello world")] - public static readonly string a4 = default; + // [String("hello world")] + public static readonly string a4 = "hello world"; /// /// Tests retrieval of the static field initialized with an initial value. @@ -82,5 +82,10 @@ public static ECPoint testGetECPoint() { return eCPoint; } + + public static string testGetString() + { + return a4; + } } } diff --git a/tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVarInit.cs b/tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVarInit.cs index e94c79044..606dc6721 100644 --- a/tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVarInit.cs +++ b/tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVarInit.cs @@ -4,7 +4,7 @@ namespace Neo.Compiler.CSharp.UnitTests.TestClasses { public class Contract_staticvar : SmartContract.Framework.SmartContract { - //define and staticvar and initit with a runtime code. + //define and static var and init it with a runtime code. static byte[] callscript = (byte[])Runtime.EntryScriptHash; public static object StaticInit() diff --git a/tests/Neo.Compiler.CSharp.TestContracts/Contract_TryCatch.cs b/tests/Neo.Compiler.CSharp.TestContracts/Contract_TryCatch.cs index 681b735d2..e3663bb4c 100644 --- a/tests/Neo.Compiler.CSharp.TestContracts/Contract_TryCatch.cs +++ b/tests/Neo.Compiler.CSharp.TestContracts/Contract_TryCatch.cs @@ -11,8 +11,8 @@ public class Contract_TryCatch : SmartContract.Framework.SmartContract private static readonly ByteString byteString2Ecpoint = default; [Hash160("NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq")] private static readonly ByteString validUInt160 = default; - [ByteArray("edcf8679104ec2911a4fe29ad7db232a493e5b990fb1da7af0c7b989948c8925")] - private static readonly byte[] validUInt256 = default; + // [ByteArray("edcf8679104ec2911a4fe29ad7db232a493e5b990fb1da7af0c7b989948c8925")] + private static readonly UInt256 validUInt256 = "edcf8679104ec2911a4fe29ad7db232a493e5b990fb1da7af0c7b989948c8925"; public static object try01() { int v = 0; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DirectInit.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DirectInit.cs new file mode 100644 index 000000000..75ae8cf96 --- /dev/null +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DirectInit.cs @@ -0,0 +1,55 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM.Types; +using System; +using Neo.SmartContract.TestEngine; + +namespace Neo.Compiler.CSharp.UnitTests +{ + [TestClass] + public class UnitTest_DirectInit + { + + [TestMethod] + public void Test_GetUInt160() + { + using var testengine = new TestEngine(snapshot: new TestDataCache()); + testengine.AddEntryScript(Utils.Extensions.TestContractRoot + "Contract_DirectInit.cs"); + var result = testengine.ExecuteTestCaseStandard("testGetUInt160"); + var value = result.Pop().GetSpan(); + + Assert.AreEqual(value.ToArray().ToHexString(), "7eee1aabeb67ed1d791d44e4f5fcf3ae9171a871"); + } + + [TestMethod] + public void Test_GetECPoint() + { + using var testengine = new TestEngine(snapshot: new TestDataCache()); + testengine.AddEntryScript(Utils.Extensions.TestContractRoot + "Contract_DirectInit.cs"); + var result = testengine.ExecuteTestCaseStandard("testGetECPoint"); + var value = result.Pop().GetSpan(); + Assert.AreEqual(value.ToHexString(), "024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9"); + } + + [TestMethod] + public void Test_GetUInt256() + { + using var testengine = new TestEngine(snapshot: new TestDataCache()); + testengine.AddEntryScript(Utils.Extensions.TestContractRoot + "Contract_DirectInit.cs"); + var result = testengine.ExecuteTestCaseStandard("testGetUInt256"); + var value = result.Pop().GetSpan(); + + Assert.AreEqual(value.ToArray().ToHexString(), "edcf8679104ec2911a4fe29ad7db232a493e5b990fb1da7af0c7b989948c8925"); + } + + [TestMethod] + public void Test_GetString() + { + using var testengine = new TestEngine(snapshot: new TestDataCache()); + testengine.AddEntryScript(Utils.Extensions.TestContractRoot + "Contract_DirectInit.cs"); + var result = testengine.ExecuteTestCaseStandard("testGetString"); + var value = result.Pop().GetString(); + + Assert.AreEqual(value, "hello world"); + } + } +} diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticVar.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticVar.cs index e8fe8c405..0fdf92a11 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticVar.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticVar.cs @@ -124,5 +124,16 @@ public void Test_GetECPoint() Assert.AreEqual(value.ToArray().ToHexString(), "024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9"); } + + [TestMethod] + public void Test_GetString() + { + using var testengine = new TestEngine(snapshot: new TestDataCache()); + testengine.AddEntryScript(Utils.Extensions.TestContractRoot + "Contract_StaticVar.cs"); + var result = testengine.ExecuteTestCaseStandard("testGetString"); + var value = result.Pop().GetString(); + + Assert.AreEqual(value, "hello world"); + } } }