Skip to content

Commit

Permalink
[Compiler Add]allow direct assignment to UInt160, UInt256, and ECPoin…
Browse files Browse the repository at this point in the history
…t. (#974)
  • Loading branch information
Jim8y authored Mar 3, 2024
1 parent ad041f1 commit c68b73e
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 30 deletions.
4 changes: 2 additions & 2 deletions examples/Example.SmartContract.Transfer/TransferContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/// <summary>
/// Transfer method that demonstrate how to transfer NEO and GAS
Expand Down
54 changes: 51 additions & 3 deletions src/Neo.Compiler.CSharp/MethodConvert/Expression/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<object?> 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;
}

Expand Down Expand Up @@ -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":
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.Compiler.CSharp/MethodConvert/MethodConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down
25 changes: 20 additions & 5 deletions src/Neo.SmartContract.Framework/ECPoint.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -39,5 +40,19 @@ public extern bool IsValid

[OpCode(OpCode.CONVERT, StackItemType.Buffer)]
public static extern explicit operator byte[](ECPoint value);

/// <summary>
/// Implicitly converts a hexadecimal string to a PublicKey object.
/// Assumes the string is a valid hexadecimal representation.
/// </summary>
/// <example>
/// PublicKey from a 33 bytes (66 characters) hexadecimal string:
/// "024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9"
/// </example>
/// <remarks>
/// This is a compile time conversion, only work with constant string.
/// If you want to convert a runtime string, convert it to byte[] first.
/// </remarks>
public static extern implicit operator ECPoint(string value);

Check warning on line 56 in src/Neo.SmartContract.Framework/ECPoint.cs

View workflow job for this annotation

GitHub Actions / Test

Method, operator, or accessor 'ECPoint.implicit operator ECPoint(string)' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.

Check warning on line 56 in src/Neo.SmartContract.Framework/ECPoint.cs

View workflow job for this annotation

GitHub Actions / PublishPackage

Method, operator, or accessor 'ECPoint.implicit operator ECPoint(string)' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.
}
}
25 changes: 20 additions & 5 deletions src/Neo.SmartContract.Framework/UInt160.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -71,5 +72,19 @@ public string ToAddress(byte version)
data = Helper.Concat(data, this);
return StdLib.Base58CheckEncode((ByteString)data);
}

/// <summary>
/// Implicitly converts a hexadecimal string to a UInt160 object.
/// This can be a 20 bytes hex string or a neo address.
/// <example>
/// 20 bytes hex string: "01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4" (no prefix)
/// Address: "NZNosnRn6FpRjwGKx8VdXv5Sn7BvzrjZVb"
/// </example>
/// <remarks>
/// This is a compile time conversion, only work with constant string.
/// If you want to convert a runtime string, convert it to byte[] first.
/// </remarks>
/// </summary>
public static extern implicit operator UInt160(string value);

Check warning on line 88 in src/Neo.SmartContract.Framework/UInt160.cs

View workflow job for this annotation

GitHub Actions / Test

Method, operator, or accessor 'UInt160.implicit operator UInt160(string)' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.

Check warning on line 88 in src/Neo.SmartContract.Framework/UInt160.cs

View workflow job for this annotation

GitHub Actions / PublishPackage

Method, operator, or accessor 'UInt160.implicit operator UInt160(string)' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.
}
}
24 changes: 19 additions & 5 deletions src/Neo.SmartContract.Framework/UInt256.cs
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -48,5 +48,19 @@ public extern bool IsValid

[OpCode(OpCode.CONVERT, StackItemType.Buffer)]
public static extern explicit operator byte[](UInt256 value);

/// <summary>
/// Implicitly converts a hexadecimal string to a UInt256 object.
/// Assumes the string is a valid hexadecimal representation.
/// <example>
/// 32 bytes (64 characters) hexadecimal string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" (no prefix)
/// </example>
/// <remarks>The input `MUST` be a valid hex string of a 32 bytes data.</remarks>
/// <remarks>
/// This is a compile time conversion, only work with constant string.
/// If you want to convert a runtime string, convert it to byte[] first.
/// </remarks>
/// </summary>
public static extern implicit operator UInt256(string value);

Check warning on line 64 in src/Neo.SmartContract.Framework/UInt256.cs

View workflow job for this annotation

GitHub Actions / Test

Method, operator, or accessor 'UInt256.implicit operator UInt256(string)' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.

Check warning on line 64 in src/Neo.SmartContract.Framework/UInt256.cs

View workflow job for this annotation

GitHub Actions / PublishPackage

Method, operator, or accessor 'UInt256.implicit operator UInt256(string)' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.
}
}
59 changes: 59 additions & 0 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_DirectInit.cs
Original file line number Diff line number Diff line change
@@ -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
{

/// <summary>
/// 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.
/// </summary>
// [PublicKey("024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9")]
private static readonly ECPoint eCPoint = "024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9";

/// <summary>
/// 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.
/// </summary>
// [Hash160("NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq")]
private static readonly UInt160 uInt160 = "NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq";

/// <summary>
/// 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.
/// </summary>
// [ByteArray("edcf8679104ec2911a4fe29ad7db232a493e5b990fb1da7af0c7b989948c8925")]
private static readonly UInt256 validUInt256 = "edcf8679104ec2911a4fe29ad7db232a493e5b990fb1da7af0c7b989948c8925";

/// <summary>
/// A static string field initialized directly.
/// This demonstrates initializing contract fields that cannot be directly assigned with their value at compile time.
/// </summary>
// [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;
}
}
}
17 changes: 11 additions & 6 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_StaticVar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
[PublicKey("024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9")]
private static readonly ECPoint eCPoint = default;
// [PublicKey("024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9")]
private static readonly ECPoint eCPoint = "024700db2e90d9f02c4f9fc862abaca92725f95b4fddcc8d7ffa538693ecf463a9";

/// <summary>
/// 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.
/// </summary>
[Hash160("NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq")]
private static readonly UInt160 uInt160 = default;
// [Hash160("NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq")]
private static readonly UInt160 uInt160 = "NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq";

/// <summary>
/// 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.
/// </summary>
[String("hello world")]
public static readonly string a4 = default;
// [String("hello world")]
public static readonly string a4 = "hello world";

/// <summary>
/// Tests retrieval of the static field initialized with an initial value.
Expand Down Expand Up @@ -82,5 +82,10 @@ public static ECPoint testGetECPoint()
{
return eCPoint;
}

public static string testGetString()
{
return a4;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_TryCatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
55 changes: 55 additions & 0 deletions tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DirectInit.cs
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
Loading

0 comments on commit c68b73e

Please sign in to comment.