Skip to content

Commit

Permalink
[Test] add instructions to the contract methods (#1191)
Browse files Browse the repository at this point in the history
* add instructions to the contract methods

* update github action

* move opcodes position in artifacts

* add base64 script to methods

* reverse change to TestCleanup.

* Fix ut

* Reduce location according to the max one

* format

* Update src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs

---------

Co-authored-by: Shargon <[email protected]>
  • Loading branch information
Jim8y and shargon authored Oct 8, 2024
1 parent f59bbbc commit b51fc46
Show file tree
Hide file tree
Showing 98 changed files with 16,544 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
env:
DOTNET_VERSION: 8.0.x
DOTNET_TEST_PARAMETERS: --no-build /p:CollectCoverage=true -l "console;verbosity=detailed"
COVERLET_EXCLUDE_COVERAGE: /p:Exclude=\"[Neo.SmartContract.TestEngine]*,[Neo.Compiler.CSharp.UnitTests]*,[Neo]*,[Neo.IO]*,[Neo.Json]*,[Neo.VM]*,[Neo.Extensions]*,[Neo.Cryptography.BLS12_381]*\"
COVERLET_EXCLUDE_COVERAGE: /p:Exclude=\"[Neo.SmartContract.TestEngine]*,[Neo.Disassembler.CSharp]*,[Neo.Compiler.CSharp.UnitTests]*,[Neo]*,[Neo.IO]*,[Neo.Json]*,[Neo.VM]*,[Neo.Extensions]*,[Neo.Cryptography.BLS12_381]*\"
COVERLET_OUTPUT: /p:CoverletOutput=${{ github.workspace }}/coverage-join/
COVERLET_MERGE_WITH: /p:MergeWith=${{ github.workspace }}/coverage-join/coverage.json

Expand Down
7 changes: 7 additions & 0 deletions neo-devpack-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.IO", "neo\src\Neo.IO\Ne
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Analyzer.UnitTests", "tests\Neo.SmartContract.Analyzer.UnitTests\Neo.SmartContract.Analyzer.UnitTests.csproj", "{F30E2375-012A-4A38-985B-31CB7DBA4D28}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Disassembler.CSharp", "src\Neo.Disassembler.CSharp\Neo.Disassembler.CSharp.csproj", "{FA988C67-43CF-4AE4-94FE-023AADFF88D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -122,6 +124,10 @@ Global
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F30E2375-012A-4A38-985B-31CB7DBA4D28}.Release|Any CPU.Build.0 = Release|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -145,6 +151,7 @@ Global
{E5EFB018-810D-4297-8921-940FA0B1ED97} = {49D5873D-7B38-48A5-B853-85146F032091}
{C2B7927F-AAA5-432A-8E76-B5080BD7EFB9} = {49D5873D-7B38-48A5-B853-85146F032091}
{F30E2375-012A-4A38-985B-31CB7DBA4D28} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{FA988C67-43CF-4AE4-94FE-023AADFF88D6} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DA935E1-C674-4364-B087-F1B511B79215}
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.Compiler.CSharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private static int ProcessOutput(Options options, string folder, CompilationCont

if (options.GenerateArtifacts != Options.GenerateArtifactsKind.None)
{
var artifact = manifest.GetArtifactsSource(baseName, nef);
var artifact = manifest.GetArtifactsSource(baseName, nef, debugInfo: debugInfo);

if (options.GenerateArtifacts.HasFlag(Options.GenerateArtifactsKind.Source))
{
Expand Down
75 changes: 75 additions & 0 deletions src/Neo.Disassembler.CSharp/Disassembler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text.RegularExpressions;
using Neo.Json;
using Neo.SmartContract;
using Neo.VM;
using OpCode = Neo.VM.OpCode;

namespace Neo.Disassembler.CSharp;

public static class Disassembler
{
private static readonly Regex RangeRegex = new(@"(\d+)\-(\d+)", RegexOptions.Compiled);

public static List<Instruction> ConvertScriptToInstructions(byte[] script)
{
var res = EnumerateInstructions(script);

return res.Select(x => x.instruction).ToList();
}

public static List<(int address, Instruction instruction)> ConvertMethodToInstructions(NefFile nef, JToken DebugInfo, string method)
{
var (start, end) = GetMethodStartEndAddress(method, DebugInfo);
var instructions = EnumerateInstructions(nef.Script).ToList();
return instructions.Where(
ai => ai.address >= start && ai.address <= end).Select(ai => (ai.address - start, ai.instruction)).ToList();
}

private static (int start, int end) GetMethodStartEndAddress(string name, JToken debugInfo)
{
name = name.Length == 0 ? string.Empty : string.Concat(name[0].ToString().ToUpper(), name.AsSpan(1)); // first letter uppercase
int start = -1, end = -1;
foreach (var method in (JArray)debugInfo["methods"]!)
{
var methodName = method!["name"]!.AsString().Split(",")[1];
if (methodName == name)
{
var rangeGroups = RangeRegex.Match(method["range"]!.AsString()).Groups;
(start, end) = (int.Parse(rangeGroups[1].ToString()), int.Parse(rangeGroups[2].ToString()));
}
}
return (start, end);
}

private static IEnumerable<(int address, Instruction instruction)> EnumerateInstructions(this Script script)
{
var address = 0;
var opcode = OpCode.PUSH0;
Instruction instruction;
for (; address < script.Length; address += instruction.Size)
{
instruction = script.GetInstruction(address);
opcode = instruction.OpCode;
yield return (address, instruction);
}
if (opcode != OpCode.RET)
yield return (address, Instruction.RET);
}

public static string InstructionToString(this Instruction instruction)
{
var opcode = instruction.OpCode.ToString();
var operand = instruction.Operand;

if (operand.IsEmpty || operand.Length == 0)
{
return $"OpCode.{opcode}";
}
var operandString = BitConverter.ToString(operand.ToArray()).Replace("-", "");
return $"OpCode.{opcode} {operandString}";
}
}
27 changes: 27 additions & 0 deletions src/Neo.Disassembler.CSharp/Neo.Disassembler.CSharp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Title>Neo.Disassembler.CSharp</Title>
<LangVersion>latest</LangVersion>
<AssemblyTitle>Neo.Disassembler.CSharp</AssemblyTitle>
<AssemblyName>Neo.Disassembler.CSharp</AssemblyName>
<RootNamespace>Neo.Disassembler.CSharp</RootNamespace>
<PackageId>Neo.Disassembler.CSharp</PackageId>
<PackageTags>NEO;Blockchain;Smart Contract</PackageTags>
<Description>Disassembler for NEO smart contract.</Description>
<IncludeContentInPack>true</IncludeContentInPack>
<ContentTargetFolders>content</ContentTargetFolders>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\neo\src\Neo.Extensions\Neo.Extensions.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.IO\Neo.IO.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.Json\Neo.Json.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.VM\Neo.VM.csproj" />
<ProjectReference Include="..\..\neo\src\Neo\Neo.csproj" />
</ItemGroup>

</Project>
140 changes: 116 additions & 24 deletions src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,98 @@
using Neo.Disassembler.CSharp;
using Neo.IO;
using Neo.Json;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Testing.TestingStandards;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Neo.SmartContract.Testing.TestingStandards;

namespace Neo.SmartContract.Testing.Extensions
{
public static class ArtifactExtensions
{
static readonly string[] _protectedWords = new string[]
{
"abstract", "as", "base", "bool", "break", "byte",
"case", "catch", "char", "checked", "class", "const",
"continue", "decimal", "default", "delegate", "do", "double",
"else", "enum", "event", "explicit", "extern", "false",
"finally", "fixed", "float", "for", "foreach", "goto",
"if", "implicit", "in", "int", "interface", "internal",
"is", "lock", "long", "namespace", "new", "null",
"object", "operator", "out", "override", "params", "private",
"protected", "public", "readonly", "ref", "return", "sbyte",
"sealed", "short", "sizeof", "stackalloc", "static", "string",
"struct", "switch", "this", "throw", "true", "try",
"typeof", "uint", "ulong", "unchecked", "unsafe", "ushort",
"using", "virtual", "void", "volatile", "while"
};
static readonly string[] _protectedWords =
[
"abstract",
"as",
"base",
"bool",
"break",
"byte",
"case",
"catch",
"char",
"checked",
"class",
"const",
"continue",
"decimal",
"default",
"delegate",
"do",
"double",
"else",
"enum",
"event",
"explicit",
"extern",
"false",
"finally",
"fixed",
"float",
"for",
"foreach",
"goto",
"if",
"implicit",
"in",
"int",
"interface",
"internal",
"is",
"lock",
"long",
"namespace",
"new",
"null",
"object",
"operator",
"out",
"override",
"params",
"private",
"protected",
"public",
"readonly",
"ref",
"return",
"sbyte",
"sealed",
"short",
"sizeof",
"stackalloc",
"static",
"string",
"struct",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"uint",
"ulong",
"unchecked",
"unsafe",
"ushort",
"using",
"virtual",
"void",
"volatile",
"while"
];

/// <summary>
/// Get source code from contract Manifest
Expand All @@ -36,10 +102,9 @@ public static class ArtifactExtensions
/// <param name="nef">Nef file</param>
/// <param name="generateProperties">Generate properties</param>
/// <returns>Source</returns>
public static string GetArtifactsSource(this ContractManifest manifest, string? name = null, NefFile? nef = null, bool generateProperties = true)
public static string GetArtifactsSource(this ContractManifest manifest, string? name = null, NefFile? nef = null, bool generateProperties = true, JToken? debugInfo = null)
{
name ??= manifest.Name;

var builder = new StringBuilder();
using var sourceCode = new StringWriter(builder)
{
Expand Down Expand Up @@ -146,7 +211,7 @@ public static string GetArtifactsSource(this ContractManifest manifest, string?

if (method.Name.StartsWith("_")) continue;

sourceCode.Write(CreateSourceMethodFromManifest(method));
sourceCode.Write(CreateSourceMethodFromManifest(method, nef, debugInfo));
sourceCode.WriteLine();
}

Expand All @@ -165,11 +230,10 @@ public static string GetArtifactsSource(this ContractManifest manifest, string?

if (method.Name.StartsWith("_")) continue;

sourceCode.Write(CreateSourceMethodFromManifest(method));
sourceCode.Write(CreateSourceMethodFromManifest(method, nef, debugInfo));
sourceCode.WriteLine();
}
sourceCode.WriteLine(" #endregion");
sourceCode.WriteLine();
}

sourceCode.WriteLine("}");
Expand Down Expand Up @@ -330,8 +394,10 @@ private static string CreateSourcePropertyFromManifest(ContractMethodDescriptor
/// Create source code from manifest method
/// </summary>
/// <param name="method">Contract method</param>
/// <param name="nefFile">Nef File</param>
/// <param name="debugInfo">Debug info</param>
/// <returns>Source</returns>
private static string CreateSourceMethodFromManifest(ContractMethodDescriptor method)
private static string CreateSourceMethodFromManifest(ContractMethodDescriptor method, NefFile? nefFile = null, JToken? debugInfo = null)
{
var methodName = TongleLowercase(EscapeName(method.Name));

Expand All @@ -344,6 +410,33 @@ private static string CreateSourceMethodFromManifest(ContractMethodDescriptor me
sourceCode.WriteLine($" /// <summary>");
sourceCode.WriteLine($" /// {(method.Safe ? "Safe method" : "Unsafe method")}");
sourceCode.WriteLine($" /// </summary>");

// Add the opcodes
if (debugInfo != null && nefFile != null)
{
var instructions = Disassembler.CSharp.Disassembler.ConvertMethodToInstructions(nefFile, debugInfo, method.Name);
if (instructions is not null && instructions.Count > 0)
{
var scripts = instructions.Select(i =>
{
var instruction = i.Item2;
var opCode = new[] { (byte)instruction.OpCode };
return instruction.Operand.Length == 0 ? opCode : opCode.Concat(instruction.Operand.ToArray());
}).SelectMany(p => p).ToArray();

var maxAddressLength = instructions.Max(instruction => instruction.address.ToString("X").Length);
var addressFormat = $"X{(maxAddressLength + 1) / 2 * 2}";

sourceCode.WriteLine(" /// <remarks>");
sourceCode.WriteLine($" /// Script: {Convert.ToBase64String(scripts)}");
foreach (var instruction in instructions)
{
sourceCode.WriteLine($" /// {instruction.address.ToString(addressFormat)} : {instruction.instruction.InstructionToString()}");
}
sourceCode.WriteLine(" /// </remarks>");
}
}

if (method.Name != methodName)
{
sourceCode.WriteLine($" [DisplayName(\"{method.Name}\")]");
Expand Down Expand Up @@ -371,7 +464,6 @@ private static string CreateSourceMethodFromManifest(ContractMethodDescriptor me
}
}


sourceCode.WriteLine(");");

return builder.ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

<ItemGroup>
<ProjectReference Include="..\..\neo\src\Neo\Neo.csproj" />
<ProjectReference Include="..\Neo.Disassembler.CSharp\Neo.Disassembler.CSharp.csproj" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion tests/Neo.Compiler.CSharp.UnitTests/TestCleanup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ internal static CompilationContext EnsureArtifactUpToDateInternal(string singleC
{
var (nef, manifest, debugInfo) = context.CreateResults(rootDebug);
var debug = NeoDebugInfo.FromDebugInfoJson(debugInfo);
var artifact = manifest.GetArtifactsSource(typeName, nef, generateProperties: true);
var artifact = manifest.GetArtifactsSource(typeName, nef, generateProperties: true, debugInfo);

var writtenArtifact = File.Exists(artifactsPath)
? await File.ReadAllTextAsync(artifactsPath).ConfigureAwait(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,19 @@ public abstract class Contract2(Neo.SmartContract.Testing.SmartContractInitializ
/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwECDAECAwTbMHBoEs5A
/// 00 : OpCode.INITSLOT 0102
/// 03 : OpCode.PUSHDATA1 01020304
/// 09 : OpCode.CONVERT 30
/// 0B : OpCode.STLOC0
/// 0C : OpCode.LDLOC0
/// 0D : OpCode.PUSH2
/// 0E : OpCode.PICKITEM
/// 0F : OpCode.RET
/// </remarks>
[DisplayName("unitTest_002")]
public abstract BigInteger? UnitTest_002(object? arg1, object? arg2 = null);

#endregion

}
Loading

0 comments on commit b51fc46

Please sign in to comment.