diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 70360d161..389959aef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,18 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Build + - name: Build Neo.Compiler.CSharp + run: dotnet build ./src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj + - name: Build Neo.SmartContract.Template and generate artifacts + run: | + dotnet pack ./src/Neo.SmartContract.Template/Neo.SmartContract.Template.csproj + dotnet new install ./src/Neo.SmartContract.Template/bin/Debug/Neo.SmartContract.Template.*.nupkg + dotnet new neocontractnep17 -n Nep17Contract -o ./src/Neo.SmartContract.Template/bin/Debug/ --force + dotnet new uninstall Neo.SmartContract.Template + dotnet remove ./src/Neo.SmartContract.Template/bin/Debug/Nep17Contract.csproj package Neo.SmartContract.Framework + dotnet add ./src/Neo.SmartContract.Template/bin/Debug/Nep17Contract.csproj reference ./src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj + dotnet ./src/Neo.Compiler.CSharp/bin/Debug/net7.0/nccs.dll -d ./src/Neo.SmartContract.Template/bin/Debug/Nep17Contract.csproj -o ./tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Artifacts/ --generate-artifacts source + - name: Build Solution run: dotnet build ./neo-devpack-dotnet.sln - name: Check format run: | @@ -39,6 +50,17 @@ jobs: run: | dotnet test ./tests/Neo.SmartContract.Framework.UnitTests \ --no-build \ + -l "console;verbosity=normal" \ + -p:CollectCoverage=true \ + -p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/lcov \ + -p:MergeWith=${GITHUB_WORKSPACE}/coverage/coverage.json \ + -p:Exclude=\"[Neo.Compiler.CSharp.UnitTests]*\" \ + -p:CoverletOutputFormat=lcov + - name: Test Neo.SmartContract.Template.UnitTests + run: | + dotnet test ./tests/Neo.SmartContract.Template.UnitTests \ + --no-build \ + -l "console;verbosity=detailed" \ -p:CollectCoverage=true \ -p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/lcov \ -p:MergeWith=${GITHUB_WORKSPACE}/coverage/coverage.json \ diff --git a/neo-devpack-dotnet.sln b/neo-devpack-dotnet.sln index 02f55daa4..0ba3f1b26 100644 --- a/neo-devpack-dotnet.sln +++ b/neo-devpack-dotnet.sln @@ -36,6 +36,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Testing", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Testing.UnitTests", "tests\Neo.SmartContract.Testing.UnitTests\Neo.SmartContract.Testing.UnitTests.csproj", "{B772B8A9-9362-4C6F-A6D3-2A4138439B2C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Template.UnitTests", "tests\Neo.SmartContract.Template.UnitTests\Neo.SmartContract.Template.UnitTests.csproj", "{17F45E0B-AB1C-4796-8C99-E5212A5592F8}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions", "neo\src\Neo.Extensions\Neo.Extensions.csproj", "{E5EFB018-810D-4297-8921-940FA0B1ED97}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.IO", "neo\src\Neo.IO\Neo.IO.csproj", "{C2B7927F-AAA5-432A-8E76-B5080BD7EFB9}" @@ -102,6 +103,10 @@ Global {B772B8A9-9362-4C6F-A6D3-2A4138439B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {B772B8A9-9362-4C6F-A6D3-2A4138439B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B772B8A9-9362-4C6F-A6D3-2A4138439B2C}.Release|Any CPU.Build.0 = Release|Any CPU + {17F45E0B-AB1C-4796-8C99-E5212A5592F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17F45E0B-AB1C-4796-8C99-E5212A5592F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17F45E0B-AB1C-4796-8C99-E5212A5592F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17F45E0B-AB1C-4796-8C99-E5212A5592F8}.Release|Any CPU.Build.0 = Release|Any CPU {E5EFB018-810D-4297-8921-940FA0B1ED97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E5EFB018-810D-4297-8921-940FA0B1ED97}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5EFB018-810D-4297-8921-940FA0B1ED97}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -129,6 +134,7 @@ Global {D6D53889-5A10-46A4-BA66-E78B56EC1881} = {49D5873D-7B38-48A5-B853-85146F032091} {648DCE6F-A0BA-4032-951B-20CF5BBFD998} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA} {B772B8A9-9362-4C6F-A6D3-2A4138439B2C} = {D5266066-0AFD-44D5-A83E-2F73668A63C8} + {17F45E0B-AB1C-4796-8C99-E5212A5592F8} = {D5266066-0AFD-44D5-A83E-2F73668A63C8} {E5EFB018-810D-4297-8921-940FA0B1ED97} = {49D5873D-7B38-48A5-B853-85146F032091} {C2B7927F-AAA5-432A-8E76-B5080BD7EFB9} = {49D5873D-7B38-48A5-B853-85146F032091} EndGlobalSection diff --git a/src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj b/src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj index 7ed9ef026..a82f6524f 100644 --- a/src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj +++ b/src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj @@ -2,6 +2,7 @@ Neo.Compiler.CSharp + net7.0 nccs Exe Neo.Compiler.CSharp diff --git a/src/Neo.Compiler.CSharp/Options.cs b/src/Neo.Compiler.CSharp/Options.cs index a6df13210..204a6b5c5 100644 --- a/src/Neo.Compiler.CSharp/Options.cs +++ b/src/Neo.Compiler.CSharp/Options.cs @@ -16,13 +16,21 @@ namespace Neo.Compiler { public class Options { + public enum GenerateArtifactsKind + { + None, + Source, + Library, + SourceAndLibrary + } + public string? Output { get; set; } public string? BaseName { get; set; } public NullableContextOptions Nullable { get; set; } public bool Checked { get; set; } public bool Debug { get; set; } public bool Assembly { get; set; } - public bool NoArtifacts { get; set; } + public GenerateArtifactsKind GenerateArtifacts { get; set; } = GenerateArtifactsKind.Source; public bool NoOptimize { get; set; } public bool NoInline { get; set; } public byte AddressVersion { get; set; } diff --git a/src/Neo.Compiler.CSharp/Program.cs b/src/Neo.Compiler.CSharp/Program.cs index 3a95e6127..190cca02a 100644 --- a/src/Neo.Compiler.CSharp/Program.cs +++ b/src/Neo.Compiler.CSharp/Program.cs @@ -42,7 +42,7 @@ static int Main(string[] args) new Option("--checked", "Indicates whether to check for overflow and underflow."), new Option(new[] { "-d", "--debug" }, "Indicates whether to generate debugging information."), new Option("--assembly", "Indicates whether to generate assembly."), - new Option("--no-artifacts", "Instruct the compiler not to generate artifacts."), + new Option("--generate-artifacts", "Instruct the compiler how to generate artifacts."), new Option("--no-optimize", "Instruct the compiler not to optimize the code."), new Option("--no-inline", "Instruct the compiler not to insert inline code."), new Option("--address-version", () => ProtocolSettings.Default.AddressVersion, "Indicates the address version used by the compiler.") @@ -188,62 +188,74 @@ private static int ProcessOutputs(Options options, string folder, CompilationCon return 1; } Console.WriteLine($"Created {path}"); - if (!options.NoArtifacts) + + if (options.GenerateArtifacts != Options.GenerateArtifactsKind.None) { - var artifact = manifest.Abi.GetArtifactsSource(baseName); - path = Path.Combine(outputFolder, $"{baseName}.artifacts.cs"); - File.WriteAllText(path, artifact); - Console.WriteLine($"Created {path}"); + var artifact = manifest.GetArtifactsSource(baseName); - try + if (options.GenerateArtifacts == Options.GenerateArtifactsKind.SourceAndLibrary || options.GenerateArtifacts == Options.GenerateArtifactsKind.Source) { - // Try to compile the artifacts into a dll - - string coreDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!; + path = Path.Combine(outputFolder, $"{baseName}.artifacts.cs"); + File.WriteAllText(path, artifact); + Console.WriteLine($"Created {path}"); + } - var syntaxTree = CSharpSyntaxTree.ParseText(artifact); - var references = new MetadataReference[] + if (options.GenerateArtifacts == Options.GenerateArtifactsKind.SourceAndLibrary || options.GenerateArtifacts == Options.GenerateArtifactsKind.Library) + { + try { - MetadataReference.CreateFromFile(Path.Combine(coreDir, "System.Runtime.dll")), - MetadataReference.CreateFromFile(Path.Combine(coreDir, "System.Runtime.InteropServices.dll")), - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DisplayNameAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Numerics.BigInteger).Assembly.Location), - MetadataReference.CreateFromFile(typeof(UInt160).Assembly.Location), - MetadataReference.CreateFromFile(typeof(SmartContract.Testing.SmartContract).Assembly.Location) - }; + // Try to compile the artifacts into a dll + + var coreDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!; + var references = new MetadataReference[] + { + MetadataReference.CreateFromFile(Path.Combine(coreDir, "System.Runtime.dll")), + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DisplayNameAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Numerics.BigInteger).Assembly.Location), + MetadataReference.CreateFromFile(typeof(NeoSystem).Assembly.Location), + MetadataReference.CreateFromFile(typeof(SmartContract.Testing.TestEngine).Assembly.Location) + }; - var compilation = CSharpCompilation.Create(baseName, new[] { syntaxTree }, references, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + CSharpCompilationOptions csOptions = new( + OutputKind.DynamicallyLinkedLibrary, + optimizationLevel: OptimizationLevel.Debug, + platform: Platform.AnyCpu, + nullableContextOptions: NullableContextOptions.Enable, + deterministic: true); - using var ms = new MemoryStream(); - EmitResult result = compilation.Emit(ms); + var syntaxTree = CSharpSyntaxTree.ParseText(artifact, options: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest)); + var compilation = CSharpCompilation.Create(baseName, new[] { syntaxTree }, references, csOptions); - if (!result.Success) - { - var failures = result.Diagnostics.Where(diagnostic => - diagnostic.IsWarningAsError || - diagnostic.Severity == DiagnosticSeverity.Error); + using var ms = new MemoryStream(); + EmitResult result = compilation.Emit(ms); - foreach (var diagnostic in failures) + if (!result.Success) { - Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); + var failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error); + + foreach (var diagnostic in failures) + { + Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); + } } - } - else - { - ms.Seek(0, SeekOrigin.Begin); + else + { + ms.Seek(0, SeekOrigin.Begin); - // Write dll + // Write dll - path = Path.Combine(outputFolder, $"{baseName}.artifacts.dll"); - File.WriteAllBytes(path, ms.ToArray()); - Console.WriteLine($"Created {path}"); + path = Path.Combine(outputFolder, $"{baseName}.artifacts.dll"); + File.WriteAllBytes(path, ms.ToArray()); + Console.WriteLine($"Created {path}"); + } + } + catch + { + Console.Error.WriteLine("Artifacts compilation error."); } - } - catch - { - Console.Error.WriteLine("Artifacts compilation error."); } } if (options.Debug) diff --git a/src/Neo.SmartContract.Template/templates/neocontractnep17/.template.config/template.json b/src/Neo.SmartContract.Template/templates/neocontractnep17/.template.config/template.json index 7a2a47621..b940f5e1c 100644 --- a/src/Neo.SmartContract.Template/templates/neocontractnep17/.template.config/template.json +++ b/src/Neo.SmartContract.Template/templates/neocontractnep17/.template.config/template.json @@ -10,7 +10,7 @@ "language": "C#", "type": "project" }, - "sourceName": "ProjectName", + "sourceName": "Nep17Contract", "symbols": { "NeoVersion": { "type": "parameter", diff --git a/src/Neo.SmartContract.Template/templates/neocontractnep17/Contract1.cs b/src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.cs similarity index 66% rename from src/Neo.SmartContract.Template/templates/neocontractnep17/Contract1.cs rename to src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.cs index 09c77719e..cf4a3b79c 100644 --- a/src/Neo.SmartContract.Template/templates/neocontractnep17/Contract1.cs +++ b/src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.cs @@ -10,15 +10,15 @@ namespace ProjectName { - [DisplayName(nameof(Contract1))] + [DisplayName(nameof(Nep17Contract))] [ManifestExtra("Author", "")] [ManifestExtra("Description", "")] [ManifestExtra("Email", "")] [ManifestExtra("Version", "")] - [ContractSourceCode("https://github.com/neo-project/neo-devpack-dotnet/tree/master/src/Neo.SmartContract.Template")] + [ContractSourceCode("https://github.com/neo-project/neo-devpack-dotnet/tree/master/src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.cs")] [ContractPermission("*", "*")] [SupportedStandards("NEP-17")] - public class Contract1 : Nep17Token + public class Nep17Contract : Nep17Token { #region Owner @@ -33,7 +33,7 @@ public static UInt160 GetOwner() private static bool IsOwner() => Runtime.CheckWitness(GetOwner()); - public delegate void OnSetOwnerDelegate(UInt160 newOwner); + public delegate void OnSetOwnerDelegate(UInt160 previousOwner, UInt160 newOwner); [DisplayName("SetOwner")] public static event OnSetOwnerDelegate OnSetOwner; @@ -45,8 +45,9 @@ public static void SetOwner(UInt160 newOwner) ExecutionEngine.Assert(newOwner.IsValid && !newOwner.IsZero, "owner must be valid"); + UInt160 previous = GetOwner(); Storage.Put(new[] { Prefix_Owner }, newOwner); - OnSetOwner(newOwner); + OnSetOwner(previous, newOwner); } #endregion @@ -75,40 +76,6 @@ public static void SetOwner(UInt160 newOwner) #endregion - #region Payment - - public static bool Withdraw(UInt160 token, UInt160 to, BigInteger amount) - { - if (IsOwner() == false) - throw new InvalidOperationException("No Authorization!"); - if (amount <= 0) - throw new ArgumentOutOfRangeException(nameof(amount)); - if (to == null || to.IsValid == false) - throw new ArgumentException("Invalid Address!"); - if (token == null || token.IsValid == false) - throw new ArgumentException("Invalid Token Address!"); - if (ContractManagement.GetContract(token) == null) - throw new ArgumentException("Token Not A Contract!"); - // TODO: Add logic - return true; - } - - // NOTE: Allows ALL NEP-17 tokens to be received for this contract - public static void OnNEP17Payment(UInt160 from, BigInteger amount, object data) - { - // TODO: Add logic for specific NEP-17 contract tokens - if (Runtime.CallingScriptHash == NEO.Hash) - { - // TODO: Add logic (Burn, Mint, Transfer, Etc) - } - if (Runtime.CallingScriptHash == GAS.Hash) - { - // TODO: Add logic (Burn, Mint, Transfer, Etc) - } - } - - #endregion - // When this contract address is included in the transaction signature, // this method will be triggered as a VerificationTrigger to verify that the signature is correct. // For example, this method needs to be called when withdrawing token from the contract. @@ -121,6 +88,7 @@ public static string MyMethod() return Storage.Get(Storage.CurrentContext, "Hello"); } + // This will be executed during deploy public static void _deploy(object data, bool update) { if (update) @@ -137,16 +105,15 @@ public static void _deploy(object data, bool update) ExecutionEngine.Assert(initialOwner.IsValid && !initialOwner.IsZero, "owner must exists"); Storage.Put(new[] { Prefix_Owner }, initialOwner); - OnSetOwner(initialOwner); - - // This will be executed during deploy + OnSetOwner(null, initialOwner); Storage.Put(Storage.CurrentContext, "Hello", "World"); } - public static void Update(ByteString nefFile, string manifest) + public static void Update(ByteString nefFile, string manifest, object data) { - if (!IsOwner()) throw new Exception("No authorization."); - ContractManagement.Update(nefFile, manifest, null); + if (IsOwner() == false) + throw new InvalidOperationException("No authorization."); + ContractManagement.Update(nefFile, manifest, data); } // NOTE: NEP-17 contracts "SHOULD NOT" have "Destroy" method diff --git a/src/Neo.SmartContract.Template/templates/neocontractnep17/ProjectName.csproj b/src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.csproj similarity index 100% rename from src/Neo.SmartContract.Template/templates/neocontractnep17/ProjectName.csproj rename to src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.csproj diff --git a/src/Neo.SmartContract.Testing/Coverage/CoverageBase.cs b/src/Neo.SmartContract.Testing/Coverage/CoverageBase.cs index ed7cbc132..1920e0335 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoverageBase.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoverageBase.cs @@ -36,7 +36,7 @@ public float CoveredPercentage var total = TotalInstructions; if (total == 0) return 0F; - return (float)CoveredInstructions / total * 100F; + return (float)CoveredInstructions / total; } } diff --git a/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs b/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs index 06e9e8a4b..c668a6bf6 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs @@ -14,10 +14,7 @@ public class CoveredContract : CoverageBase { #region Internal - /// - /// Coverage Data - /// - internal Dictionary CoverageData { get; } = new(); + private readonly Dictionary _coverageData = new(); #endregion @@ -29,12 +26,12 @@ public class CoveredContract : CoverageBase /// /// Methods /// - public CoveredMethod[] Methods { get; } + public CoveredMethod[] Methods { get; private set; } /// /// Coverage /// - public override IEnumerable Coverage => CoverageData.Values; + public override IEnumerable Coverage => _coverageData.Values; /// /// CoveredContract @@ -51,14 +48,7 @@ public CoveredContract(UInt160 hash, ContractAbi? abi, Script? script) // Extract all methods - if (abi is not null) - { - Methods = abi.Methods - .Select(u => CreateMethod(abi, script, u)) - .Where(u => u is not null) - .OrderBy(u => u!.Offset) - .ToArray()!; - } + GenerateMethods(abi, script); // Iterate all valid instructions @@ -67,12 +57,24 @@ public CoveredContract(UInt160 hash, ContractAbi? abi, Script? script) while (ip < script.Length) { var instruction = script.GetInstruction(ip); - CoverageData[ip] = new CoverageHit(ip, false); + _coverageData[ip] = new CoverageHit(ip, false); ip += instruction.Size; } } - private CoveredMethod? CreateMethod(ContractAbi abi, Script script, ContractMethodDescriptor abiMethod) + internal void GenerateMethods(ContractAbi? abi, Script? script) + { + Methods = Array.Empty(); + + if (script is null || abi is null) return; + + Methods = abi.Methods + .Select(s => CreateMethod(abi, script, s)) + .OrderBy(o => o.Offset) + .ToArray()!; + } + + private CoveredMethod CreateMethod(ContractAbi abi, Script script, ContractMethodDescriptor abiMethod) { var to = script.Length - 1; var next = abi.Methods.OrderBy(u => u.Offset).Where(u => u.Offset > abiMethod.Offset).FirstOrDefault(); @@ -111,21 +113,26 @@ public CoveredContract(UInt160 hash, ContractAbi? abi, Script? script) /// Join coverage /// /// Coverage - public void Join(IEnumerable coverage) + public void Join(IEnumerable? coverage) { + if (coverage is null || coverage.Any() == false) return; + // Join the coverage between them foreach (var c in coverage) { if (c.Hits == 0) continue; - if (CoverageData.TryGetValue(c.Offset, out var kvpValue)) - { - kvpValue.Hit(c); - } - else + lock (_coverageData) { - CoverageData.Add(c.Offset, c.Clone()); + if (_coverageData.TryGetValue(c.Offset, out var kvpValue)) + { + kvpValue.Hit(c); + } + else + { + _coverageData.Add(c.Offset, c.Clone()); + } } } } @@ -136,32 +143,64 @@ public void Join(IEnumerable coverage) /// Coverage dump public string Dump() { - // TODO: improve dump later - var builder = new StringBuilder(); using var sourceCode = new StringWriter(builder) { NewLine = "\n" }; - var cover = CoveredPercentage.ToString("0.00").ToString(); - sourceCode.WriteLine($"| {Hash,-50} | {cover,7}% |"); + var cover = $"{CoveredPercentage:P2}"; + sourceCode.WriteLine($"{Hash} [{cover}]"); - foreach (var method in Methods) + List rows = new(); + var max = new int[] { "Method".Length, "Line ".Length }; + + foreach (var method in Methods.OrderBy(u => u.Method.Name).OrderByDescending(u => u.CoveredPercentage)) { - sourceCode.WriteLine(method.Dump()); + cover = $"{method.CoveredPercentage:P2}"; + rows.Add(new string[] { method.Method.ToString(), cover }); + + max[0] = Math.Max(method.Method.ToString().Length, max[0]); + max[1] = Math.Max(cover.Length, max[1]); } + sourceCode.WriteLine($"┌-{"─".PadLeft(max[0], '─')}-┬-{"─".PadLeft(max[1], '─')}-┐"); + sourceCode.WriteLine($"│ {string.Format($"{{0,-{max[0]}}}", "Method", max[0])} │ {string.Format($"{{0,{max[1]}}}", "Line ", max[1])} │"); + sourceCode.WriteLine($"├-{"─".PadLeft(max[0], '─')}-┼-{"─".PadLeft(max[1], '─')}-┤"); + + foreach (var print in rows) + { + sourceCode.WriteLine($"│ {string.Format($"{{0,-{max[0]}}}", print[0], max[0])} │ {string.Format($"{{0,{max[1]}}}", print[1], max[1])} │"); + } + + sourceCode.WriteLine($"└-{"─".PadLeft(max[0], '─')}-┴-{"─".PadLeft(max[1], '─')}-┘"); + return builder.ToString(); } /// - /// String representation + /// Hit /// - /// - public override string ToString() + /// Instruction pointer + /// Gas + public void Hit(int instructionPointer, long gas) { - return $"Hash:{Hash}"; + lock (_coverageData) + { + if (!_coverageData.TryGetValue(instructionPointer, out var coverage)) + { + // Note: This call is unusual, out of the expected + + _coverageData[instructionPointer] = coverage = new CoverageHit(instructionPointer, true); + } + coverage.Hit(gas); + } } + + /// + /// String representation + /// + /// Hash + public override string ToString() => Hash.ToString(); } } diff --git a/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs b/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs index 2dd1708b0..15a8b4276 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs @@ -46,19 +46,6 @@ public CoveredMethod(CoveredContract contract, ContractMethodDescriptor method, MethodLength = methodLength; } - /// - /// Dump coverage - /// - /// Coverage dump - public string Dump() - { - // TODO: improve dump later - - var cover = CoveredPercentage.ToString("0.00").ToString(); - - return $"| {Method,50} | {cover,7}% |"; - } - public override string ToString() => Method.ToString(); } } diff --git a/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs b/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs index 0b260af0b..27e3f6251 100644 --- a/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs +++ b/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs @@ -1,4 +1,5 @@ using Neo.SmartContract.Manifest; +using Neo.SmartContract.Testing.TestingStandards; using System; using System.Collections.Generic; using System.IO; @@ -9,7 +10,8 @@ namespace Neo.SmartContract.Testing.Extensions { public static class ArtifactExtensions { - static readonly string[] _protectedWords = new string[] { + static readonly string[] _protectedWords = new string[] + { "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", @@ -26,20 +28,31 @@ public static class ArtifactExtensions }; /// - /// Get source code from contract Abi + /// Get source code from contract Manifest /// - /// Abi - /// Contract name + /// Manifest + /// Class name, by default is manifest.Name /// Generate properties /// Source - public static string GetArtifactsSource(this ContractAbi abi, string name, bool generateProperties = true) + public static string GetArtifactsSource(this ContractManifest manifest, string? name = null, bool generateProperties = true) { + name ??= manifest.Name; + var builder = new StringBuilder(); using var sourceCode = new StringWriter(builder) { NewLine = "\n" }; + var inheritance = new List + { + typeof(SmartContract) + }; + + if (manifest.IsNep17()) inheritance.Add(typeof(INep17Standard)); + if (manifest.IsOwnable()) inheritance.Add(typeof(IOwnable)); + if (manifest.IsVerificable()) inheritance.Add(typeof(IVerificable)); + sourceCode.WriteLine("using Neo.Cryptography.ECC;"); sourceCode.WriteLine("using System.Collections.Generic;"); sourceCode.WriteLine("using System.ComponentModel;"); @@ -47,47 +60,54 @@ public static string GetArtifactsSource(this ContractAbi abi, string name, bool sourceCode.WriteLine(""); sourceCode.WriteLine("namespace Neo.SmartContract.Testing;"); sourceCode.WriteLine(""); - sourceCode.WriteLine($"public abstract class {name} : Neo.SmartContract.Testing.SmartContract"); + sourceCode.WriteLine($"public abstract class {name} : " + string.Join(", ", inheritance)); sourceCode.WriteLine("{"); // Crete events - if (abi.Events.Any()) + if (manifest.Abi.Events.Any()) { sourceCode.WriteLine(" #region Events"); + sourceCode.WriteLine(); - foreach (var ev in abi.Events.OrderBy(u => u.Name)) + foreach (var ev in manifest.Abi.Events.OrderBy(u => u.Name)) { - sourceCode.Write(CreateSourceEventFromManifest(ev)); + sourceCode.Write(CreateSourceEventFromManifest(ev, inheritance)); + sourceCode.WriteLine(); } sourceCode.WriteLine(" #endregion"); + sourceCode.WriteLine(); } // Create methods - var methods = abi.Methods; + var methods = manifest.Abi.Methods; if (generateProperties) { - (methods, var properties) = ProcessAbiMethods(abi.Methods); + (methods, var properties) = ProcessAbiMethods(manifest.Abi.Methods); if (properties.Any()) { sourceCode.WriteLine(" #region Properties"); + sourceCode.WriteLine(); foreach (var property in properties.OrderBy(u => u.getter.Name)) { sourceCode.Write(CreateSourcePropertyFromManifest(property.getter, property.setter)); + sourceCode.WriteLine(); } sourceCode.WriteLine(" #endregion"); + sourceCode.WriteLine(); } } if (methods.Any(u => u.Safe)) { sourceCode.WriteLine(" #region Safe methods"); + sourceCode.WriteLine(); foreach (var method in methods.Where(u => u.Safe).OrderBy(u => u.Name)) { @@ -96,14 +116,17 @@ public static string GetArtifactsSource(this ContractAbi abi, string name, bool if (method.Name.StartsWith("_")) continue; sourceCode.Write(CreateSourceMethodFromManifest(method)); + sourceCode.WriteLine(); } sourceCode.WriteLine(" #endregion"); + sourceCode.WriteLine(); } if (methods.Any(u => !u.Safe)) { sourceCode.WriteLine(" #region Unsafe methods"); + sourceCode.WriteLine(); foreach (var method in methods.Where(u => !u.Safe).OrderBy(u => u.Name)) { @@ -112,14 +135,18 @@ public static string GetArtifactsSource(this ContractAbi abi, string name, bool if (method.Name.StartsWith("_")) continue; sourceCode.Write(CreateSourceMethodFromManifest(method)); + sourceCode.WriteLine(); } sourceCode.WriteLine(" #endregion"); + sourceCode.WriteLine(); } // Create constructor sourceCode.WriteLine(" #region Constructor for internal use only"); + sourceCode.WriteLine(); sourceCode.WriteLine($" protected {name}(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) {{ }}"); + sourceCode.WriteLine(); sourceCode.WriteLine(" #endregion"); sourceCode.WriteLine("}"); @@ -165,18 +192,54 @@ private static (ContractMethodDescriptor[] methods, (ContractMethodDescriptor ge /// Create source code from event /// /// Event + /// Inheritance /// Source - private static string CreateSourceEventFromManifest(ContractEventDescriptor ev) + private static string CreateSourceEventFromManifest(ContractEventDescriptor ev, IList inheritance) { - var evName = TongleLowercase(EscapeName(ev.Name)); - if (!evName.StartsWith("On")) evName = "On" + evName; - var builder = new StringBuilder(); using var sourceCode = new StringWriter(builder) { NewLine = "\n" }; + switch (ev.Name) + { + case "Transfer": + { + if (inheritance.Contains(typeof(INep17Standard)) && ev.Parameters.Length == 3 && + ev.Parameters[0].Type == ContractParameterType.Hash160 && + ev.Parameters[1].Type == ContractParameterType.Hash160 && + ev.Parameters[2].Type == ContractParameterType.Integer) + { + sourceCode.WriteLine($" [DisplayName(\"{ev.Name}\")]"); + sourceCode.WriteLine(" public event Neo.SmartContract.Testing.TestingStandards.INep17Standard.delTransfer? OnTransfer;"); + return builder.ToString(); + } + + break; + } + case "SetOwner": + { + if (inheritance.Contains(typeof(IOwnable)) && ev.Parameters.Length == 2 && + ev.Parameters[0].Type == ContractParameterType.Hash160 && + ev.Parameters[1].Type == ContractParameterType.Hash160) + { + sourceCode.WriteLine($" [DisplayName(\"{ev.Name}\")]"); + sourceCode.WriteLine(" public event Neo.SmartContract.Testing.TestingStandards.IOwnable.delSetOwner? OnSetOwner;"); + return builder.ToString(); + } + + break; + } + } + + // Add On prefix + + var evName = TongleLowercase(EscapeName(ev.Name)); + if (!evName.StartsWith("On")) evName = "On" + evName; + + // Compose delegate + sourceCode.Write($" public delegate void del{ev.Name}("); var isFirst = true; @@ -189,6 +252,10 @@ private static string CreateSourceEventFromManifest(ContractEventDescriptor ev) } sourceCode.WriteLine(");"); + sourceCode.WriteLine(); + + // Compose event + if (ev.Name != evName) { sourceCode.WriteLine($" [DisplayName(\"{ev.Name}\")]"); @@ -214,6 +281,10 @@ private static string CreateSourcePropertyFromManifest(ContractMethodDescriptor { NewLine = "\n" }; + + sourceCode.WriteLine($" /// "); + sourceCode.WriteLine($" /// {(getter.Safe ? "Safe property" : "Unsafe property")}"); + sourceCode.WriteLine($" /// "); sourceCode.WriteLine($" public abstract {TypeToSource(getter.ReturnType)} {propertyName} {getset}"); return builder.ToString(); @@ -256,7 +327,7 @@ private static string CreateSourceMethodFromManifest(ContractMethodDescriptor me { // it will be object X, we can add a default value - sourceCode.Write($"{TypeToSource(arg.Type)}? {EscapeName(arg.Name)} = null"); + sourceCode.Write($"{TypeToSource(arg.Type)} {EscapeName(arg.Name)} = null"); } else { @@ -307,18 +378,18 @@ private static string TypeToSource(ContractParameterType type) { return type switch { - ContractParameterType.Boolean => "bool", - ContractParameterType.Integer => "BigInteger", - ContractParameterType.String => "string", - ContractParameterType.Hash160 => "UInt160", - ContractParameterType.Hash256 => "UInt256", - ContractParameterType.PublicKey => "ECPoint", - ContractParameterType.ByteArray => "byte[]", - ContractParameterType.Signature => "byte[]", - ContractParameterType.Array => "IList", - ContractParameterType.Map => "IDictionary", + ContractParameterType.Boolean => "bool?", + ContractParameterType.Integer => "BigInteger?", + ContractParameterType.String => "string?", + ContractParameterType.Hash160 => "UInt160?", + ContractParameterType.Hash256 => "UInt256?", + ContractParameterType.PublicKey => "ECPoint?", + ContractParameterType.ByteArray => "byte[]?", + ContractParameterType.Signature => "byte[]?", + ContractParameterType.Array => "IList?", + ContractParameterType.Map => "IDictionary?", ContractParameterType.Void => "void", - _ => "object", + _ => "object?", }; } } diff --git a/src/Neo.SmartContract.Testing/Extensions/StandardExtensions.cs b/src/Neo.SmartContract.Testing/Extensions/StandardExtensions.cs new file mode 100644 index 000000000..565fd184c --- /dev/null +++ b/src/Neo.SmartContract.Testing/Extensions/StandardExtensions.cs @@ -0,0 +1,47 @@ +using Neo.SmartContract.Manifest; +using System; +using System.Linq; + +namespace Neo.SmartContract.Testing.Extensions +{ + public static class StandardExtensions + { + /// + /// Is Nep17 contract + /// + /// Manifest + /// True if NEP-17 + public static bool IsNep17(this ContractManifest manifest) + { + return manifest.SupportedStandards.Contains("NEP-17"); + } + + /// + /// Is Ownable + /// + /// Manifest + /// True if is Ownable + public static bool IsOwnable(this ContractManifest manifest) + { + return + manifest.Abi.Methods + .Any(u => u.Name == "getOwner" && u.Safe && u.Parameters.Length == 0) && + manifest.Abi.Methods + .Any(u => u.Name == "setOwner" && !u.Safe && u.Parameters.Length == 1 && u.Parameters[0].Type == ContractParameterType.Hash160) && + manifest.Abi.Events + .Any(u => u.Name == "SetOwner" && u.Parameters.Length == 2 && + u.Parameters[0].Type == ContractParameterType.Hash160 && + u.Parameters[1].Type == ContractParameterType.Hash160); + } + + /// + /// Is Verificable + /// + /// Manifest + /// True if is Verificable + public static bool IsVerificable(this ContractManifest manifest) + { + return manifest.Abi.Methods.Any(u => u.Name == "verify" && u.Safe && u.Parameters.Length == 0); + } + } +} diff --git a/src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs b/src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs index 379927630..71f1c8a9c 100644 --- a/src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs +++ b/src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs @@ -1,4 +1,5 @@ -using Neo.IO; +using Neo.Cryptography.ECC; +using Neo.SmartContract.Iterators; using Neo.VM.Types; using System; using System.Collections.Generic; @@ -10,38 +11,6 @@ namespace Neo.SmartContract.Testing.Extensions { public static class TestExtensions { - /// - /// Convert dotnet type to stack item - /// - /// Data - /// StackItem - public static StackItem ConvertToStackItem(this object? data) - { - return data switch - { - null => StackItem.Null, - bool b => (VM.Types.Boolean)b, - string s => (ByteString)s, - byte[] d => (ByteString)d, - ReadOnlyMemory r => (ByteString)r, - byte by => (Integer)by, - sbyte sby => (Integer)sby, - short i16 => (Integer)i16, - ushort ui16 => (Integer)ui16, - int i32 => (Integer)i32, - uint ui32 => (Integer)ui32, - long i64 => (Integer)i64, - ulong ui64 => (Integer)ui64, - BigInteger bi => (Integer)bi, - UInt160 u160 => (ByteString)u160.ToArray(), - UInt256 u256 => (ByteString)u256.ToArray(), - Cryptography.ECC.ECPoint ec => (ByteString)ec.ToArray(), - object[] arr => new VM.Types.Array(arr.Select(ConvertToStackItem)), - IEnumerable iarr => new VM.Types.Array(iarr.Select(ConvertToStackItem)), - _ => StackItem.Null, - }; - } - /// /// Convert Array stack item to dotnet array /// @@ -77,28 +46,63 @@ public static StackItem ConvertToStackItem(this object? data) return type switch { - _ when type == typeof(bool) => stackItem.GetBoolean(), _ when type == typeof(string) => Utility.StrictUTF8.GetString(stackItem.GetSpan()), _ when type == typeof(byte[]) => stackItem.GetSpan().ToArray(), + + _ when type == typeof(bool) => stackItem.GetBoolean(), + _ when type == typeof(bool?) => stackItem.GetBoolean(), _ when type == typeof(byte) => (byte)stackItem.GetInteger(), + _ when type == typeof(byte?) => (byte)stackItem.GetInteger(), _ when type == typeof(sbyte) => (sbyte)stackItem.GetInteger(), + _ when type == typeof(sbyte?) => (sbyte)stackItem.GetInteger(), _ when type == typeof(short) => (short)stackItem.GetInteger(), + _ when type == typeof(short?) => (short)stackItem.GetInteger(), _ when type == typeof(ushort) => (ushort)stackItem.GetInteger(), + _ when type == typeof(ushort?) => (ushort)stackItem.GetInteger(), _ when type == typeof(int) => (int)stackItem.GetInteger(), + _ when type == typeof(int?) => (int)stackItem.GetInteger(), _ when type == typeof(uint) => (uint)stackItem.GetInteger(), + _ when type == typeof(uint?) => (uint)stackItem.GetInteger(), _ when type == typeof(long) => (long)stackItem.GetInteger(), + _ when type == typeof(long?) => (long)stackItem.GetInteger(), _ when type == typeof(ulong) => (ulong)stackItem.GetInteger(), + _ when type == typeof(ulong?) => (ulong)stackItem.GetInteger(), + + _ when type.IsEnum => Enum.ToObject(type, (int)stackItem.GetInteger()), _ when type == typeof(BigInteger) => stackItem.GetInteger(), + _ when type == typeof(BigInteger?) => stackItem.GetInteger(), _ when type == typeof(UInt160) => new UInt160(stackItem.GetSpan().ToArray()), _ when type == typeof(UInt256) => new UInt256(stackItem.GetSpan().ToArray()), - _ when type == typeof(Cryptography.ECC.ECPoint) => Cryptography.ECC.ECPoint.FromBytes(stackItem.GetSpan().ToArray(), Cryptography.ECC.ECCurve.Secp256r1), + _ when type == typeof(ECPoint) => ECPoint.FromBytes(stackItem.GetSpan().ToArray(), ECCurve.Secp256r1), _ when type == typeof(List) && stackItem is CompoundType cp => new List(cp.SubItems), // SubItems in StackItem type _ when typeof(IInteroperable).IsAssignableFrom(type) => CreateInteroperable(stackItem, type), + _ when type.IsArray && stackItem is CompoundType cp => CreateTypeArray(cp.SubItems, type.GetElementType()!), + _ when stackItem is InteropInterface it && it.GetInterface().GetType() == type => it.GetInterface(), + _ => throw new FormatException($"Impossible to convert {stackItem} to {type}"), }; } - private static IInteroperable CreateInteroperable(StackItem stackItem, Type type) + private static object CreateTypeArray(IEnumerable objects, Type elementType) + { + var obj = objects.ToArray(); + + if (elementType != typeof(object)) + { + var arr = System.Array.CreateInstance(elementType, obj.Length); + + for (int x = 0; x < arr.Length; x++) + { + arr.SetValue(ConvertTo(obj[x], elementType), x); + } + + return arr; + } + + return obj; + } + + private static object CreateInteroperable(StackItem stackItem, Type type) { var interoperable = (IInteroperable)Activator.CreateInstance(type)!; interoperable.FromStackItem(stackItem); diff --git a/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt160.cs b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt160.cs new file mode 100644 index 000000000..c578928aa --- /dev/null +++ b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt160.cs @@ -0,0 +1,15 @@ +namespace Neo.SmartContract.Testing.InvalidTypes +{ + public class InvalidUInt160 + { + /// + /// Null UInt160 + /// + public static readonly UInt160? Null = null; + + /// + /// This will be an invalid UInt160 + /// + public static readonly UInt160 Invalid = new(); + } +} diff --git a/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt256.cs b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt256.cs new file mode 100644 index 000000000..90a63ab8b --- /dev/null +++ b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt256.cs @@ -0,0 +1,15 @@ +namespace Neo.SmartContract.Testing.InvalidTypes +{ + public class InvalidUInt256 + { + /// + /// Null UInt256 + /// + public static readonly UInt256? Null = null; + + /// + /// This will be an invalid UInt256 + /// + public static readonly UInt256 Invalid = new(); + } +} diff --git a/src/Neo.SmartContract.Testing/Native/ContractManagement.cs b/src/Neo.SmartContract.Testing/Native/ContractManagement.cs index b348e26d1..01c73a8be 100644 --- a/src/Neo.SmartContract.Testing/Native/ContractManagement.cs +++ b/src/Neo.SmartContract.Testing/Native/ContractManagement.cs @@ -1,72 +1,100 @@ -using Neo.Cryptography.ECC; -using System.Collections.Generic; +using Neo.SmartContract.Iterators; using System.ComponentModel; using System.Numerics; namespace Neo.SmartContract.Testing; -public abstract class ContractManagement : Neo.SmartContract.Testing.SmartContract +public abstract class ContractManagement : SmartContract { #region Events + public delegate void delDeploy(UInt160 Hash); + [DisplayName("Deploy")] public event delDeploy? OnDeploy; public delegate void delDestroy(UInt160 Hash); + [DisplayName("Destroy")] public event delDestroy? OnDestroy; public delegate void delUpdate(UInt160 Hash); + [DisplayName("Update")] public event delUpdate? OnUpdate; #endregion + #region Properties - public abstract object ContractHashes { [DisplayName("getContractHashes")] get; } + + /// + /// Safe property + /// + public abstract IIterator ContractHashes { [DisplayName("getContractHashes")] get; } + + /// + /// Safe property + /// public abstract BigInteger MinimumDeploymentFee { [DisplayName("getMinimumDeploymentFee")] get; [DisplayName("setMinimumDeploymentFee")] set; } + #endregion + #region Safe methods + /// /// Safe method /// [DisplayName("getContract")] public abstract ContractState GetContract(UInt160 hash); + /// /// Safe method /// [DisplayName("getContractById")] public abstract ContractState GetContractById(BigInteger id); + /// /// Safe method /// [DisplayName("hasMethod")] public abstract bool HasMethod(UInt160 hash, string method, BigInteger pcount); + #endregion + #region Unsafe methods + /// /// Unsafe method /// [DisplayName("deploy")] public abstract ContractState Deploy(byte[] nefFile, byte[] manifest); + /// /// Unsafe method /// [DisplayName("deploy")] public abstract ContractState Deploy(byte[] nefFile, byte[] manifest, object? data = null); + /// /// Unsafe method /// [DisplayName("destroy")] public abstract void Destroy(); + /// /// Unsafe method /// [DisplayName("update")] public abstract void Update(byte[] nefFile, byte[] manifest); + /// /// Unsafe method /// [DisplayName("update")] public abstract void Update(byte[] nefFile, byte[] manifest, object? data = null); + #endregion + #region Constructor for internal use only - protected ContractManagement(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected ContractManagement(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/Native/CryptoLib.cs b/src/Neo.SmartContract.Testing/Native/CryptoLib.cs index a6498111e..54b50f1cd 100644 --- a/src/Neo.SmartContract.Testing/Native/CryptoLib.cs +++ b/src/Neo.SmartContract.Testing/Native/CryptoLib.cs @@ -1,65 +1,77 @@ -using Neo.Cryptography.ECC; -using System.Collections.Generic; using System.ComponentModel; using System.Numerics; namespace Neo.SmartContract.Testing; -public abstract class CryptoLib : Neo.SmartContract.Testing.SmartContract +public abstract class CryptoLib : SmartContract { #region Safe methods + /// /// Safe method /// [DisplayName("bls12381Add")] - public abstract object Bls12381Add(object x, object y); + public abstract object? Bls12381Add(object? x, object? y); + /// /// Safe method /// [DisplayName("bls12381Deserialize")] - public abstract object Bls12381Deserialize(byte[] data); + public abstract object? Bls12381Deserialize(byte[]? data); + /// /// Safe method /// [DisplayName("bls12381Equal")] - public abstract bool Bls12381Equal(object x, object y); + public abstract bool? Bls12381Equal(object? x, object? y); + /// /// Safe method /// [DisplayName("bls12381Mul")] - public abstract object Bls12381Mul(object x, byte[] mul, bool neg); + public abstract object? Bls12381Mul(object? x, byte[]? mul, bool? neg); + /// /// Safe method /// [DisplayName("bls12381Pairing")] - public abstract object Bls12381Pairing(object g1, object g2); + public abstract object? Bls12381Pairing(object? g1, object? g2); + /// /// Safe method /// [DisplayName("bls12381Serialize")] - public abstract byte[] Bls12381Serialize(object g); + public abstract byte[]? Bls12381Serialize(object? g); + /// /// Safe method /// [DisplayName("murmur32")] - public abstract byte[] Murmur32(byte[] data, BigInteger seed); + public abstract byte[] Murmur32(byte[]? data, BigInteger? seed); + /// /// Safe method /// [DisplayName("ripemd160")] - public abstract byte[] Ripemd160(byte[] data); + public abstract byte[] Ripemd160(byte[]? data); + /// /// Safe method /// [DisplayName("sha256")] - public abstract byte[] Sha256(byte[] data); + public abstract byte[] Sha256(byte[]? data); + /// /// Safe method /// [DisplayName("verifyWithECDsa")] - public abstract bool VerifyWithECDsa(byte[] message, byte[] pubkey, byte[] signature, BigInteger curve); + public abstract bool VerifyWithECDsa(byte[]? message, byte[]? pubkey, byte[]? signature, BigInteger? curve); + #endregion + #region Constructor for internal use only - protected CryptoLib(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected CryptoLib(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/Native/GasToken.cs b/src/Neo.SmartContract.Testing/Native/GasToken.cs index 9816da823..568aad13d 100644 --- a/src/Neo.SmartContract.Testing/Native/GasToken.cs +++ b/src/Neo.SmartContract.Testing/Native/GasToken.cs @@ -1,37 +1,61 @@ -using Neo.Cryptography.ECC; -using System.Collections.Generic; using System.ComponentModel; using System.Numerics; namespace Neo.SmartContract.Testing; -public abstract class GasToken : Neo.SmartContract.Testing.SmartContract +public abstract class GasToken : SmartContract { #region Events - public delegate void delTransfer(UInt160 from, UInt160 to, BigInteger amount); + + public delegate void delTransfer(UInt160? from, UInt160? to, BigInteger? amount); + [DisplayName("Transfer")] public event delTransfer? OnTransfer; + #endregion + #region Properties + + /// + /// Safe property + /// public abstract BigInteger Decimals { [DisplayName("decimals")] get; } + + /// + /// Safe property + /// public abstract string Symbol { [DisplayName("symbol")] get; } + + /// + /// Safe property + /// public abstract BigInteger TotalSupply { [DisplayName("totalSupply")] get; } + #endregion + #region Safe methods + /// /// Safe method /// [DisplayName("balanceOf")] - public abstract BigInteger BalanceOf(UInt160 account); + public abstract BigInteger BalanceOf(UInt160? account); + #endregion + #region Unsafe methods + /// /// Unsafe method /// [DisplayName("transfer")] - public abstract bool Transfer(UInt160 from, UInt160 to, BigInteger amount, object? data = null); + public abstract bool Transfer(UInt160? from, UInt160? to, BigInteger? amount, object? data = null); + #endregion + #region Constructor for internal use only - protected GasToken(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected GasToken(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/Native/LedgerContract.cs b/src/Neo.SmartContract.Testing/Native/LedgerContract.cs index ac6c663b1..142a0d852 100644 --- a/src/Neo.SmartContract.Testing/Native/LedgerContract.cs +++ b/src/Neo.SmartContract.Testing/Native/LedgerContract.cs @@ -1,49 +1,70 @@ -using Neo.Cryptography.ECC; -using System.Collections.Generic; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using Neo.VM; using System.ComponentModel; using System.Numerics; namespace Neo.SmartContract.Testing; -public abstract class LedgerContract : Neo.SmartContract.Testing.SmartContract +public abstract class LedgerContract : SmartContract { #region Properties + + /// + /// Safe property + /// public abstract UInt256 CurrentHash { [DisplayName("currentHash")] get; } + + /// + /// Safe property + /// public abstract BigInteger CurrentIndex { [DisplayName("currentIndex")] get; } + #endregion + #region Safe methods + /// /// Safe method /// [DisplayName("getBlock")] - public abstract IList GetBlock(byte[] indexOrHash); + public abstract TrimmedBlock? GetBlock(byte[]? indexOrHash); + /// /// Safe method /// [DisplayName("getTransaction")] - public abstract IList GetTransaction(UInt256 hash); + public abstract Transaction? GetTransaction(UInt256? hash); + /// /// Safe method /// [DisplayName("getTransactionFromBlock")] - public abstract IList GetTransactionFromBlock(byte[] blockIndexOrHash, BigInteger txIndex); + public abstract Transaction? GetTransactionFromBlock(byte[]? blockIndexOrHash, BigInteger? txIndex); + /// /// Safe method /// [DisplayName("getTransactionHeight")] - public abstract BigInteger GetTransactionHeight(UInt256 hash); + public abstract BigInteger? GetTransactionHeight(UInt256? hash); + /// /// Safe method /// [DisplayName("getTransactionSigners")] - public abstract IList GetTransactionSigners(UInt256 hash); + public abstract Signer[]? GetTransactionSigners(UInt256? hash); + /// /// Safe method /// [DisplayName("getTransactionVMState")] - public abstract BigInteger GetTransactionVMState(UInt256 hash); + public abstract VMState GetTransactionVMState(UInt256? hash); + #endregion + #region Constructor for internal use only - protected LedgerContract(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected LedgerContract(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/Native/NeoToken.cs b/src/Neo.SmartContract.Testing/Native/NeoToken.cs index 79e5599e4..82fe6139e 100644 --- a/src/Neo.SmartContract.Testing/Native/NeoToken.cs +++ b/src/Neo.SmartContract.Testing/Native/NeoToken.cs @@ -1,79 +1,171 @@ using Neo.Cryptography.ECC; -using System.Collections.Generic; +using Neo.IO; +using Neo.SmartContract.Iterators; +using Neo.VM; +using Neo.VM.Types; +using System; using System.ComponentModel; using System.Numerics; +using Neo.SmartContract.Testing.Extensions; +using System.Linq; namespace Neo.SmartContract.Testing; -public abstract class NeoToken : Neo.SmartContract.Testing.SmartContract +public abstract class NeoToken : SmartContract { + public class Candidate : IInteroperable + { + /// + /// Public key + /// + public ECPoint? PublicKey { get; private set; } + + /// + /// Votes + /// + public BigInteger Votes { get; private set; } = BigInteger.Zero; + + public void FromStackItem(StackItem stackItem) + { + if (stackItem is not CompoundType cp) throw new FormatException(); + if (cp.Count < 2) throw new FormatException(); + + var items = cp.SubItems.ToArray(); + + PublicKey = (ECPoint)items[0].ConvertTo(typeof(ECPoint))!; + Votes = (BigInteger)items[1].ConvertTo(typeof(BigInteger))!; + } + + public StackItem ToStackItem(ReferenceCounter referenceCounter) + { + return new VM.Types.Array(new StackItem[] { PublicKey.ToArray(), Votes }); + } + } + #region Events + public delegate void delCandidateStateChanged(ECPoint pubkey, bool registered, BigInteger votes); [DisplayName("CandidateStateChanged")] public event delCandidateStateChanged? OnCandidateStateChanged; + public delegate void delTransfer(UInt160 from, UInt160 to, BigInteger amount); [DisplayName("Transfer")] public event delTransfer? OnTransfer; + public delegate void delVote(UInt160 account, ECPoint from, ECPoint to, BigInteger amount); [DisplayName("Vote")] public event delVote? OnVote; + #endregion + #region Properties + + /// + /// Safe property + /// public abstract BigInteger Decimals { [DisplayName("decimals")] get; } - public abstract object AllCandidates { [DisplayName("getAllCandidates")] get; } - public abstract IList Candidates { [DisplayName("getCandidates")] get; } - public abstract IList Committee { [DisplayName("getCommittee")] get; } + + /// + /// Safe property + /// + public abstract IIterator AllCandidates { [DisplayName("getAllCandidates")] get; } + + /// + /// Safe property + /// + public abstract Candidate[] Candidates { [DisplayName("getCandidates")] get; } + + /// + /// Safe property + /// + public abstract ECPoint[] Committee { [DisplayName("getCommittee")] get; } + + /// + /// Safe property + /// public abstract BigInteger GasPerBlock { [DisplayName("getGasPerBlock")] get; [DisplayName("setGasPerBlock")] set; } - public abstract IList NextBlockValidators { [DisplayName("getNextBlockValidators")] get; } + + /// + /// Safe property + /// + public abstract ECPoint[] NextBlockValidators { [DisplayName("getNextBlockValidators")] get; } + + /// + /// Safe property + /// public abstract BigInteger RegisterPrice { [DisplayName("getRegisterPrice")] get; [DisplayName("setRegisterPrice")] set; } + + /// + /// Safe property + /// public abstract string Symbol { [DisplayName("symbol")] get; } + + /// + /// Safe property + /// public abstract BigInteger TotalSupply { [DisplayName("totalSupply")] get; } + #endregion + #region Safe methods + /// /// Safe method /// [DisplayName("balanceOf")] - public abstract BigInteger BalanceOf(UInt160 account); + public abstract BigInteger BalanceOf(UInt160? account); + /// /// Safe method /// [DisplayName("getAccountState")] - public abstract IList GetAccountState(UInt160 account); + public abstract Native.NeoToken.NeoAccountState GetAccountState(UInt160? account); + /// /// Safe method /// [DisplayName("getCandidateVote")] - public abstract BigInteger GetCandidateVote(ECPoint pubKey); + public abstract BigInteger GetCandidateVote(ECPoint? pubKey); + /// /// Safe method /// [DisplayName("unclaimedGas")] - public abstract BigInteger UnclaimedGas(UInt160 account, BigInteger end); + public abstract BigInteger UnclaimedGas(UInt160? account, BigInteger? end); + #endregion + #region Unsafe methods + /// /// Unsafe method /// [DisplayName("registerCandidate")] - public abstract bool RegisterCandidate(ECPoint pubkey); + public abstract bool RegisterCandidate(ECPoint? pubkey); + /// /// Unsafe method /// [DisplayName("transfer")] - public abstract bool Transfer(UInt160 from, UInt160 to, BigInteger amount, object? data = null); + public abstract bool Transfer(UInt160? from, UInt160? to, BigInteger? amount, object? data = null); + /// /// Unsafe method /// [DisplayName("unregisterCandidate")] - public abstract bool UnregisterCandidate(ECPoint pubkey); + public abstract bool UnregisterCandidate(ECPoint? pubkey); + /// /// Unsafe method /// [DisplayName("vote")] - public abstract bool Vote(UInt160 account, ECPoint voteTo); + public abstract bool Vote(UInt160? account, ECPoint? voteTo); + #endregion + #region Constructor for internal use only - protected NeoToken(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected NeoToken(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/Native/OracleContract.cs b/src/Neo.SmartContract.Testing/Native/OracleContract.cs index 1964d8dfb..3c83fd3d0 100644 --- a/src/Neo.SmartContract.Testing/Native/OracleContract.cs +++ b/src/Neo.SmartContract.Testing/Native/OracleContract.cs @@ -1,37 +1,55 @@ -using Neo.Cryptography.ECC; -using System.Collections.Generic; using System.ComponentModel; using System.Numerics; namespace Neo.SmartContract.Testing; -public abstract class OracleContract : Neo.SmartContract.Testing.SmartContract +public abstract class OracleContract : SmartContract { #region Events + public delegate void delOracleRequest(BigInteger Id, UInt160 RequestContract, string Url, string Filter); [DisplayName("OracleRequest")] public event delOracleRequest? OnOracleRequest; + public delegate void delOracleResponse(BigInteger Id, UInt256 OriginalTx); [DisplayName("OracleResponse")] public event delOracleResponse? OnOracleResponse; + #endregion + #region Properties + + /// + /// Safe property + /// public abstract BigInteger Price { [DisplayName("getPrice")] get; [DisplayName("setPrice")] set; } + + /// + /// Safe property + /// public abstract bool Verify { [DisplayName("verify")] get; } + #endregion + #region Unsafe methods + /// /// Unsafe method /// [DisplayName("finish")] public abstract void Finish(); + /// /// Unsafe method /// [DisplayName("request")] - public abstract void Request(string url, string filter, string callback, object userData, BigInteger gasForResponse); + public abstract void Request(string? url, string? filter, string? callback, object? userData, BigInteger? gasForResponse); + #endregion + #region Constructor for internal use only - protected OracleContract(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected OracleContract(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/Native/PolicyContract.cs b/src/Neo.SmartContract.Testing/Native/PolicyContract.cs index 6be01a1b8..fc9737f24 100644 --- a/src/Neo.SmartContract.Testing/Native/PolicyContract.cs +++ b/src/Neo.SmartContract.Testing/Native/PolicyContract.cs @@ -1,47 +1,70 @@ -using Neo.Cryptography.ECC; -using System.Collections.Generic; using System.ComponentModel; using System.Numerics; namespace Neo.SmartContract.Testing; -public abstract class PolicyContract : Neo.SmartContract.Testing.SmartContract +public abstract class PolicyContract : SmartContract { #region Properties + + /// + /// Safe property + /// public abstract BigInteger ExecFeeFactor { [DisplayName("getExecFeeFactor")] get; [DisplayName("setExecFeeFactor")] set; } + + /// + /// Safe property + /// public abstract BigInteger FeePerByte { [DisplayName("getFeePerByte")] get; [DisplayName("setFeePerByte")] set; } + + /// + /// Safe property + /// public abstract BigInteger StoragePrice { [DisplayName("getStoragePrice")] get; [DisplayName("setStoragePrice")] set; } + #endregion + #region Safe methods + /// /// Safe method /// [DisplayName("getAttributeFee")] - public abstract BigInteger GetAttributeFee(BigInteger attributeType); + public abstract BigInteger GetAttributeFee(BigInteger? attributeType); + /// /// Safe method /// [DisplayName("isBlocked")] - public abstract bool IsBlocked(UInt160 account); + public abstract bool IsBlocked(UInt160? account); + #endregion + #region Unsafe methods + /// /// Unsafe method /// [DisplayName("blockAccount")] - public abstract bool BlockAccount(UInt160 account); + public abstract bool BlockAccount(UInt160? account); + /// /// Unsafe method /// [DisplayName("setAttributeFee")] - public abstract void SetAttributeFee(BigInteger attributeType, BigInteger value); + public abstract void SetAttributeFee(BigInteger? attributeType, BigInteger? value); + /// /// Unsafe method /// [DisplayName("unblockAccount")] - public abstract bool UnblockAccount(UInt160 account); + public abstract bool UnblockAccount(UInt160? account); + #endregion + #region Constructor for internal use only - protected PolicyContract(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected PolicyContract(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/Native/RoleManagement.cs b/src/Neo.SmartContract.Testing/Native/RoleManagement.cs index 1bb7aa07d..62eed6f97 100644 --- a/src/Neo.SmartContract.Testing/Native/RoleManagement.cs +++ b/src/Neo.SmartContract.Testing/Native/RoleManagement.cs @@ -1,32 +1,43 @@ using Neo.Cryptography.ECC; -using System.Collections.Generic; +using Neo.SmartContract.Native; using System.ComponentModel; using System.Numerics; namespace Neo.SmartContract.Testing; -public abstract class RoleManagement : Neo.SmartContract.Testing.SmartContract +public abstract class RoleManagement : SmartContract { #region Events + public delegate void delDesignation(BigInteger Role, BigInteger BlockIndex); [DisplayName("Designation")] public event delDesignation? OnDesignation; + #endregion + #region Safe methods + /// /// Safe method /// [DisplayName("getDesignatedByRole")] - public abstract IList GetDesignatedByRole(BigInteger role, BigInteger index); + public abstract ECPoint[] GetDesignatedByRole(BigInteger? role, BigInteger? index); + #endregion + #region Unsafe methods + /// /// Unsafe method /// [DisplayName("designateAsRole")] - public abstract void DesignateAsRole(BigInteger role, IList nodes); + public abstract void DesignateAsRole(Role? role, ECPoint[]? nodes); + #endregion + #region Constructor for internal use only - protected RoleManagement(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected RoleManagement(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/Native/StdLib.cs b/src/Neo.SmartContract.Testing/Native/StdLib.cs index 04fe6f613..b687b9025 100644 --- a/src/Neo.SmartContract.Testing/Native/StdLib.cs +++ b/src/Neo.SmartContract.Testing/Native/StdLib.cs @@ -1,120 +1,143 @@ -using Neo.Cryptography.ECC; -using System.Collections.Generic; using System.ComponentModel; using System.Numerics; namespace Neo.SmartContract.Testing; -public abstract class StdLib : Neo.SmartContract.Testing.SmartContract +public abstract class StdLib : SmartContract { #region Safe methods + /// /// Safe method /// [DisplayName("atoi")] - public abstract BigInteger Atoi(string value); + public abstract BigInteger Atoi(string? value); + /// /// Safe method /// [DisplayName("atoi")] - public abstract BigInteger Atoi(string value, BigInteger @base); + public abstract BigInteger Atoi(string? value, BigInteger? @base); + /// /// Safe method /// [DisplayName("base58CheckDecode")] - public abstract byte[] Base58CheckDecode(string s); + public abstract byte[] Base58CheckDecode(string? s); + /// /// Safe method /// [DisplayName("base58CheckEncode")] - public abstract string Base58CheckEncode(byte[] data); + public abstract string Base58CheckEncode(byte[]? data); + /// /// Safe method /// [DisplayName("base58Decode")] - public abstract byte[] Base58Decode(string s); + public abstract byte[] Base58Decode(string? s); + /// /// Safe method /// [DisplayName("base58Encode")] - public abstract string Base58Encode(byte[] data); + public abstract string Base58Encode(byte[]? data); + /// /// Safe method /// [DisplayName("base64Decode")] - public abstract byte[] Base64Decode(string s); + public abstract byte[] Base64Decode(string? s); + /// /// Safe method /// [DisplayName("base64Encode")] - public abstract string Base64Encode(byte[] data); + public abstract string Base64Encode(byte[]? data); + /// /// Safe method /// [DisplayName("deserialize")] - public abstract object Deserialize(byte[] data); + public abstract object Deserialize(byte[]? data); + /// /// Safe method /// [DisplayName("itoa")] - public abstract string Itoa(BigInteger value); + public abstract string Itoa(BigInteger? value); + /// /// Safe method /// [DisplayName("itoa")] - public abstract string Itoa(BigInteger value, BigInteger @base); + public abstract string Itoa(BigInteger? value, BigInteger? @base); + /// /// Safe method /// [DisplayName("jsonDeserialize")] - public abstract object JsonDeserialize(byte[] json); + public abstract object JsonDeserialize(byte[]? json); + /// /// Safe method /// [DisplayName("jsonSerialize")] public abstract byte[] JsonSerialize(object? item = null); + /// /// Safe method /// [DisplayName("memoryCompare")] - public abstract BigInteger MemoryCompare(byte[] str1, byte[] str2); + public abstract BigInteger MemoryCompare(byte[]? str1, byte[]? str2); + /// /// Safe method /// [DisplayName("memorySearch")] - public abstract BigInteger MemorySearch(byte[] mem, byte[] value); + public abstract BigInteger MemorySearch(byte[]? mem, byte[]? value); + /// /// Safe method /// [DisplayName("memorySearch")] - public abstract BigInteger MemorySearch(byte[] mem, byte[] value, BigInteger start); + public abstract BigInteger MemorySearch(byte[]? mem, byte[]? value, BigInteger? start); + /// /// Safe method /// [DisplayName("memorySearch")] - public abstract BigInteger MemorySearch(byte[] mem, byte[] value, BigInteger start, bool backward); + public abstract BigInteger MemorySearch(byte[]? mem, byte[]? value, BigInteger? start, bool? backward); + /// /// Safe method /// [DisplayName("serialize")] public abstract byte[] Serialize(object? item = null); + /// /// Safe method /// [DisplayName("stringSplit")] - public abstract IList StringSplit(string str, string separator); + public abstract string[] StringSplit(string? str, string? separator); + /// /// Safe method /// [DisplayName("stringSplit")] - public abstract IList StringSplit(string str, string separator, bool removeEmptyEntries); + public abstract string[] StringSplit(string? str, string? separator, bool? removeEmptyEntries); + /// /// Safe method /// [DisplayName("strLen")] - public abstract BigInteger StrLen(string str); + public abstract BigInteger StrLen(string? str); + #endregion + #region Constructor for internal use only - protected StdLib(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + + protected StdLib(SmartContractInitialize initialize) : base(initialize) { } + #endregion } diff --git a/src/Neo.SmartContract.Testing/NativeArtifacts.cs b/src/Neo.SmartContract.Testing/NativeArtifacts.cs index caa7b5f73..7cc88c494 100644 --- a/src/Neo.SmartContract.Testing/NativeArtifacts.cs +++ b/src/Neo.SmartContract.Testing/NativeArtifacts.cs @@ -52,7 +52,7 @@ public class NativeArtifacts public RoleManagement RoleManagement { get; } /// - /// OracleContract + /// StdLib /// public StdLib StdLib { get; } diff --git a/src/Neo.SmartContract.Testing/Neo.SmartContract.Testing.csproj b/src/Neo.SmartContract.Testing/Neo.SmartContract.Testing.csproj index 15676ae7b..dd3a11f79 100644 --- a/src/Neo.SmartContract.Testing/Neo.SmartContract.Testing.csproj +++ b/src/Neo.SmartContract.Testing/Neo.SmartContract.Testing.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Neo.SmartContract.Testing/README.md b/src/Neo.SmartContract.Testing/README.md index aab24d859..fd900ba9e 100644 --- a/src/Neo.SmartContract.Testing/README.md +++ b/src/Neo.SmartContract.Testing/README.md @@ -33,7 +33,7 @@ The **Neo.SmartContract.Testing** project is designed to facilitate the developm The process of generating the artifacts, or the source code necessary to interact with the contract, is extremely simple. There are two main ways to do it: -1. Using the `ABI` of a contract, the necessary source code to interact with the contract can be generated by calling the `GetArtifactsSource` method available in the `Neo.SmartContract.Testing.Extensions` namespace, we will only have to specify the name of our resulting class, which will usually be the same as the one existing in the `Name` field of the manifest. +1. Using the `ContractManifest` of a contract, the necessary source code to interact with the contract can be generated by calling the `GetArtifactsSource` method available in the `Neo.SmartContract.Testing.Extensions` namespace, we will only have to specify the name of our resulting class, which will usually be the same as the one existing in the `Name` field of the manifest. 2. Through the Neo C# compiler, automatically when compiling a contract in C#, the necessary source code to interact with the contract is generated. This is available in the same path as the generated .nef file, and its extension are `.artifacts.cs` and `.artifacts.dll`. @@ -49,7 +49,7 @@ public class MyUnitTestClass foreach (var n in Native.NativeContract.Contracts) { var manifest = n.Manifest; - var source = manifest.Abi.GetArtifactsSource(manifest.Name); + var source = manifest.GetArtifactsSource(); File.WriteAllText($"{manifest.Name}.cs", source); } @@ -344,4 +344,4 @@ Keep in mind that the coverage is at the instruction level. The currently known limitations are: - Receive events during the deploy, because the object is returned after performing the deploy, it is not possible to intercept notifications for the deploy unless the contract is previously created with `FromHash` knowing the hash of the contract to be created. -- It is possible that if the contract is updated, the coverage calculation may be incorrect. +- It is possible that if the contract is updated, the coverage calculation may be incorrect. The update method of a contract can be tested, but if the same script and abi as the original are not used, it can result in a coverage calculation error. diff --git a/src/Neo.SmartContract.Testing/SmartContract.cs b/src/Neo.SmartContract.Testing/SmartContract.cs index 21fb37847..042173802 100644 --- a/src/Neo.SmartContract.Testing/SmartContract.cs +++ b/src/Neo.SmartContract.Testing/SmartContract.cs @@ -51,7 +51,31 @@ internal StackItem Invoke(string methodName, params object[] args) // Compose script using ScriptBuilder script = new(); - script.EmitDynamicCall(Hash, methodName, args); + + if (args is null || args.Length == 0) + script.Emit(OpCode.NEWARRAY0); + else + { + for (int i = args.Length - 1; i >= 0; i--) + { + var arg = args[i]; + + if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.Invalid) || + ReferenceEquals(arg, InvalidTypes.InvalidUInt256.Invalid)) + { + arg = System.Array.Empty(); + } + + script.EmitPush(arg); + } + script.EmitPush(args.Length); + script.Emit(OpCode.PACK); + } + + script.EmitPush(CallFlags.All); + script.EmitPush(methodName); + script.EmitPush(Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); // Execute @@ -79,7 +103,9 @@ internal void InvokeOnNotify(string eventName, VM.Types.Array state) var ev = _contractType.GetEvent(eventName); if (ev is null) { - ev = _contractType.GetEvents().FirstOrDefault(u => u.GetCustomAttribute()?.DisplayName == eventName); + ev = _contractType.GetEvents() + .FirstOrDefault(u => u.Name == eventName || u.GetCustomAttribute(true)?.DisplayName == eventName); + if (ev is null) { _notifyCache[eventName] = null; diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index e6a5ec171..0d0fe4a58 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -233,6 +233,18 @@ internal void ApplicationEngineLog(object? sender, LogEventArgs e) #endregion + /// + /// Get deploy hash + /// + /// Nef + /// Manifest + /// Contract hash + public UInt160 GetDeployHash(byte[] nef, string manifest) + { + return Helper.GetContractHash(Sender, + nef.AsSerializable().CheckSum, ContractManifest.Parse(manifest).Name); + } + /// /// Deploy Smart contract /// @@ -265,7 +277,19 @@ public T Deploy(NefFile nef, ContractManifest manifest, object? data = null, // Mock contract //UInt160 hash = Helper.GetContractHash(Sender, nef.CheckSum, manifest.Name); - return MockContract(state.Hash, state.Id, customMock); + var ret = MockContract(state.Hash, state.Id, customMock); + + // We cache the coverage contract during `_deploy` + // at this moment we don't have the abi stored + // so we need to regenerate the coverage methods + + if (EnableCoverageCapture) + { + var coverage = GetCoverage(ret); + coverage?.GenerateMethods(state.Manifest.Abi, state.Script); + } + + return ret; } /// @@ -457,7 +481,11 @@ public StackItem Execute(Script script) { if (!Coverage.TryGetValue(contract.Hash, out var coveredContract)) { - return null; + var state = Neo.SmartContract.Native.NativeContract.ContractManagement.GetContract(Storage.Snapshot, contract.Hash); + if (state == null) return null; + + coveredContract = new(contract.Hash, state.Manifest.Abi, state.Script); + Coverage[coveredContract.Hash] = coveredContract; } return coveredContract; @@ -471,9 +499,7 @@ public StackItem Execute(Script script) /// CoveredContract public CoverageBase? GetCoverage(T contract, string methodName, int pcount) where T : SmartContract { - var coveredContract = GetCoverage(contract); - - return coveredContract?.GetCoverage(methodName, pcount); + return GetCoverage(contract)?.GetCoverage(methodName, pcount); } /// @@ -485,10 +511,8 @@ public StackItem Execute(Script script) /// CoveredContract public CoverageBase? GetCoverage(T contract, Expression> method) where T : SmartContract { - if (!Coverage.TryGetValue(contract.Hash, out var coveredContract)) - { - return null; - } + var coveredContract = GetCoverage(contract); + if (coveredContract == null) return null; var abiMethods = AbiMethod.CreateFromExpression(method.Body) .Select(coveredContract.GetCoverage) @@ -514,10 +538,8 @@ public StackItem Execute(Script script) /// CoveredContract public CoverageBase? GetCoverage(T contract, Expression> method) where T : SmartContract { - if (!Coverage.TryGetValue(contract.Hash, out var coveredContract)) - { - return null; - } + var coveredContract = GetCoverage(contract); + if (coveredContract == null) return null; var abiMethods = AbiMethod.CreateFromExpression(method.Body) .Select(coveredContract.GetCoverage) diff --git a/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs b/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs index 72b1df211..3ce77e5d5 100644 --- a/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs +++ b/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs @@ -1,6 +1,5 @@ using Neo.Network.P2P.Payloads; using Neo.Persistence; -using Neo.SmartContract.Testing.Coverage; using Neo.SmartContract.Testing.Extensions; using Neo.VM; using System; @@ -66,14 +65,7 @@ protected override void PostExecuteInstruction(Instruction instruction) if (InstructionPointer is null) return; - if (!coveredContract.CoverageData.TryGetValue(InstructionPointer.Value, out var coverage)) - { - // Note: This call is unusual, out of the expected - - coveredContract.CoverageData[InstructionPointer.Value] = coverage = new CoverageHit(InstructionPointer.Value, true); - } - - coverage.Hit(GasConsumed - PreExecuteInstructionGasConsumed); + coveredContract.Hit(InstructionPointer.Value, GasConsumed - PreExecuteInstructionGasConsumed); } protected override void OnSysCall(InteropDescriptor descriptor) diff --git a/src/Neo.SmartContract.Testing/TestingStandards/INep17Standard.cs b/src/Neo.SmartContract.Testing/TestingStandards/INep17Standard.cs new file mode 100644 index 000000000..d17e6d96f --- /dev/null +++ b/src/Neo.SmartContract.Testing/TestingStandards/INep17Standard.cs @@ -0,0 +1,55 @@ +using System.ComponentModel; +using System.Numerics; + +namespace Neo.SmartContract.Testing.TestingStandards; + +public interface INep17Standard +{ + #region Events + + public delegate void delTransfer(UInt160? from, UInt160? to, BigInteger? amount); + + [DisplayName("Transfer")] + public event delTransfer? OnTransfer; + + #endregion + + #region Properties + + /// + /// Safe method + /// + public string? Symbol { [DisplayName("symbol")] get; } + + /// + /// Safe method + /// + public BigInteger? Decimals { [DisplayName("decimals")] get; } + + /// + /// Safe method + /// + public BigInteger? TotalSupply { [DisplayName("totalSupply")] get; } + + #endregion + + #region Safe methods + + /// + /// Safe method + /// + [DisplayName("balanceOf")] + public BigInteger? BalanceOf(UInt160? owner); + + #endregion + + #region Unsafe methods + + /// + /// Unsafe method + /// + [DisplayName("transfer")] + public bool? Transfer(UInt160? from, UInt160? to, BigInteger? amount, object? data = null); + + #endregion +} diff --git a/src/Neo.SmartContract.Testing/TestingStandards/IOwnable.cs b/src/Neo.SmartContract.Testing/TestingStandards/IOwnable.cs new file mode 100644 index 000000000..6d8500a21 --- /dev/null +++ b/src/Neo.SmartContract.Testing/TestingStandards/IOwnable.cs @@ -0,0 +1,24 @@ +using System.ComponentModel; + +namespace Neo.SmartContract.Testing.TestingStandards; + +public interface IOwnable +{ + #region Events + + public delegate void delSetOwner(UInt160? previousOwner, UInt160? newOwner); + + [DisplayName("SetOwner")] + public event delSetOwner? OnSetOwner; + + #endregion + + #region Properties + + /// + /// Safe property + /// + public UInt160? Owner { [DisplayName("getOwner")] get; [DisplayName("setOwner")] set; } + + #endregion +} diff --git a/src/Neo.SmartContract.Testing/TestingStandards/IVerificable.cs b/src/Neo.SmartContract.Testing/TestingStandards/IVerificable.cs new file mode 100644 index 000000000..ec0a3b8f6 --- /dev/null +++ b/src/Neo.SmartContract.Testing/TestingStandards/IVerificable.cs @@ -0,0 +1,11 @@ +using System.ComponentModel; + +namespace Neo.SmartContract.Testing.TestingStandards; + +public interface IVerificable +{ + /// + /// Safe property + /// + public bool? Verify { [DisplayName("verify")] get; } +} diff --git a/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs b/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs new file mode 100644 index 000000000..3b0fa94e6 --- /dev/null +++ b/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs @@ -0,0 +1,156 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Testing.InvalidTypes; +using Neo.VM; +using System.Collections.Generic; +using System.Numerics; + +namespace Neo.SmartContract.Testing.TestingStandards; + +public class Nep17Tests : TestBase + where T : SmartContract, INep17Standard +{ + /// + /// Expected total supply + /// + public virtual BigInteger ExpectedTotalSupply => 0; + + /// + /// Expected Decimals + /// + public virtual byte ExpectedDecimals => 8; + + /// + /// Expected symbol + /// + public virtual string ExpectedSymbol => "EXAMPLE"; + + #region Transfer event checks + + private List<(UInt160? from, UInt160? to, BigInteger? amount)> raisedTransfer = new(); + + #endregion + + /// + /// Initialize Test + /// + public Nep17Tests(string nefFile, string manifestFile) : base(nefFile, manifestFile) + { + Contract.OnTransfer += onTransfer; + } + + void onTransfer(UInt160? from, UInt160? to, BigInteger? amount) + { + raisedTransfer.Add((from, to, amount)); + } + + #region Asserts + + /// + /// Assert that Transfer event was raised + /// + /// From + /// To + /// Amount + public void AssertTransferEvent(UInt160? from, UInt160? to, BigInteger? amount) + { + Assert.AreEqual(1, raisedTransfer.Count); + Assert.AreEqual(raisedTransfer[0].from, from); + Assert.AreEqual(raisedTransfer[0].to, to); + Assert.AreEqual(raisedTransfer[0].amount, amount); + raisedTransfer.Clear(); + } + + /// + /// Assert that Transfer event was NOT raised + /// + public void AssertNoTransferEvent() + { + Assert.AreEqual(0, raisedTransfer.Count); + } + + #endregion + + #region Tests + + [TestMethod] + public virtual void TestDecimals() + { + Assert.AreEqual(ExpectedDecimals, Contract.Decimals); + } + + [TestMethod] + public virtual void TestSymbol() + { + Assert.AreEqual(ExpectedSymbol, Contract.Symbol); + } + + [TestMethod] + public virtual void TestTotalSupply() + { + Assert.AreEqual(ExpectedTotalSupply, Contract.TotalSupply); + } + + [TestMethod] + public virtual void TestBalanceOf() + { + Assert.AreEqual(0, Contract.BalanceOf(Bob.Account)); + Assert.ThrowsException(() => Contract.BalanceOf(InvalidUInt160.Null)); + Assert.ThrowsException(() => Contract.BalanceOf(InvalidUInt160.Invalid)); + } + + [TestMethod] + public virtual void TestTransfer() + { + // Invoke transfer from Alice to Bob + + Engine.SetTransactionSigners(Alice); + + var initialSupply = Contract.TotalSupply; + var fromBalance = Contract.BalanceOf(Alice.Account); + + Assert.IsTrue(fromBalance > 5, "Alice needs at least 5 tokens"); + Assert.IsTrue(Contract.Transfer(Alice.Account, Bob.Account, 3)); + + Assert.AreEqual(fromBalance - 3, Contract.BalanceOf(Alice.Account)); + Assert.AreEqual(3, Contract.BalanceOf(Bob.Account)); + Assert.AreEqual(initialSupply, Contract.TotalSupply); + AssertTransferEvent(Alice.Account, Bob.Account, 3); + + // Invoke invalid transfers + + Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, Bob.Account, -1))); + Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(InvalidUInt160.Null, Bob.Account, -1))); + Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.Null, 0))); + + Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, Bob.Account, -1))); + Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(InvalidUInt160.Invalid, Bob.Account, -1))); + Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.Invalid, 0))); + + // Invoke transfer without signature + + Engine.SetTransactionSigners(Bob); + Assert.IsFalse(Contract.Transfer(Alice.Account, Bob.Account, 1)); + AssertNoTransferEvent(); + + // Check with more balance + + Assert.IsFalse(Contract.Transfer(Bob.Account, Alice.Account, 4)); + AssertNoTransferEvent(); + + // Check with not signed + + Assert.IsFalse(Contract.Transfer(Alice.Account, Bob.Account, 0)); + AssertNoTransferEvent(); + + // Return the balance to Allice + + Assert.IsTrue(Contract.Transfer(Bob.Account, Alice.Account, 3)); + + Assert.AreEqual(fromBalance, Contract.BalanceOf(Alice.Account)); + Assert.AreEqual(0, Contract.BalanceOf(Bob.Account)); + Assert.AreEqual(initialSupply, Contract.TotalSupply); + AssertTransferEvent(Bob.Account, Alice.Account, 3); + } + + #endregion +} diff --git a/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs b/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs new file mode 100644 index 000000000..3ab6cf3bb --- /dev/null +++ b/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs @@ -0,0 +1,114 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Testing.InvalidTypes; +using Neo.VM; +using System; +using System.Collections.Generic; + +namespace Neo.SmartContract.Testing.TestingStandards; + +public class OwnableTests : TestBase + where T : SmartContract, IOwnable +{ + #region Transfer event checks + + private List<(UInt160? from, UInt160? to)> raisedOnChangeOwner = new(); + + #endregion + + /// + /// Initialize Test + /// + public OwnableTests(string nefFile, string manifestFile) : base(nefFile, manifestFile) + { + Contract.OnSetOwner += onSetOwner; + } + + void onSetOwner(UInt160? from, UInt160? to) + { + raisedOnChangeOwner.Add((from, to)); + } + + #region Asserts + + /// + /// Assert that OnChangeOwner event was raised + /// + /// From + /// To + public void AssertOnChangeOwnerEvent(UInt160? from, UInt160? to) + { + Assert.AreEqual(1, raisedOnChangeOwner.Count); + Assert.AreEqual(raisedOnChangeOwner[0].from, from); + Assert.AreEqual(raisedOnChangeOwner[0].to, to); + raisedOnChangeOwner.Clear(); + } + + /// + /// Assert that Transfer event was NOT raised + /// + public void AssertNoOnChangeOwnerEvent() + { + Assert.AreEqual(0, raisedOnChangeOwner.Count); + } + + #endregion + + #region Tests + + [TestMethod] + public virtual void TestVerify() + { + if (Contract is IVerificable verificable) + { + Engine.SetTransactionSigners(Alice); + Assert.IsTrue(verificable.Verify); + Engine.SetTransactionSigners(TestEngine.GetNewSigner()); + Assert.IsFalse(verificable.Verify); + } + } + + [TestMethod] + public virtual void TestSenderAsDefaultOwner() + { + var random = TestEngine.GetNewSigner(); + + Engine.SetTransactionSigners(random); + + var expectedHash = Engine.GetDeployHash(NefFile, Manifest); + var check = Engine.FromHash(expectedHash, false); + + check.OnSetOwner += onSetOwner; + var ownable = Engine.Deploy(NefFile, Manifest, null); + Assert.AreEqual(check.Hash, ownable.Hash); + check.OnSetOwner -= onSetOwner; + + AssertOnChangeOwnerEvent(null, random.Account); + Assert.AreEqual(random.Account, ownable.Owner); + } + + [TestMethod] + public virtual void TestSetGetOwner() + { + // Alice is the deployer + + Assert.AreEqual(Alice.Account, Contract.Owner); + Engine.SetTransactionSigners(Bob); + Assert.ThrowsException(() => Contract.Owner = Bob.Account); + + Engine.SetTransactionSigners(Alice); + Assert.ThrowsException(() => Contract.Owner = UInt160.Zero); + Assert.ThrowsException(() => Contract.Owner = InvalidUInt160.Null); + Assert.ThrowsException(() => Contract.Owner = InvalidUInt160.Invalid); + + Contract.Owner = Bob.Account; + Assert.AreEqual(Bob.Account, Contract.Owner); + Assert.ThrowsException(() => Contract.Owner = Bob.Account); + + Engine.SetTransactionSigners(Bob); + + Contract.Owner = Alice.Account; + Assert.AreEqual(Alice.Account, Contract.Owner); + } + + #endregion +} diff --git a/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs b/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs new file mode 100644 index 000000000..92c39fd0e --- /dev/null +++ b/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs @@ -0,0 +1,46 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Testing.Coverage; +using System.IO; + +namespace Neo.SmartContract.Testing.TestingStandards; + +public class TestBase where T : SmartContract +{ + public static CoveredContract? Coverage { get; private set; } + public static Signer Alice { get; } = TestEngine.GetNewSigner(); + public static Signer Bob { get; } = TestEngine.GetNewSigner(); + + public byte[] NefFile { get; } + public string Manifest { get; } + public TestEngine Engine { get; } + public T Contract { get; } + public UInt160 ContractHash => Contract.Hash; + + /// + /// Initialize Test + /// + public TestBase(string nefFile, string manifestFile) + { + NefFile = File.ReadAllBytes(nefFile); + Manifest = File.ReadAllText(manifestFile); + + Engine = new TestEngine(true); + Engine.SetTransactionSigners(Alice); + Contract = Engine.Deploy(NefFile, Manifest, null); + + if (Coverage is null) + { + Coverage = Contract.GetCoverage()!; + Assert.IsNotNull(Coverage); + } + } + + [TestCleanup] + public virtual void OnCleanup() + { + // Join the current coverage into the static one + + Coverage?.Join(Contract.GetCoverage()); + } +} diff --git a/tests/Neo.SmartContract.Template.UnitTests/Neo.SmartContract.Template.UnitTests.csproj b/tests/Neo.SmartContract.Template.UnitTests/Neo.SmartContract.Template.UnitTests.csproj new file mode 100644 index 000000000..252976257 --- /dev/null +++ b/tests/Neo.SmartContract.Template.UnitTests/Neo.SmartContract.Template.UnitTests.csproj @@ -0,0 +1,34 @@ + + + + net7.0 + enable + latest + enable + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/CoverageContractTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/CoverageContractTests.cs new file mode 100644 index 000000000..f242a2a8b --- /dev/null +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/CoverageContractTests.cs @@ -0,0 +1,27 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Neo.SmartContract.Template.UnitTests.templates.neocontractnep17 +{ + [TestClass] + public class CoverageContractTests + { + /// + /// Required coverage to be success + /// + public static float RequiredCoverage { get; set; } = 0.95F; + + [AssemblyCleanup] + public static void EnsureCoverage() + { + // Ennsure that the coverage is more than X% at the end of the tests + + var coverage = Nep17ContractTests.Coverage; + coverage?.Join(OwnerContractTests.Coverage); + + Assert.IsNotNull(coverage); + + Console.WriteLine(coverage.Dump()); + Assert.IsTrue(coverage.CoveredPercentage > RequiredCoverage, $"Coverage is less than {RequiredCoverage:P2}"); + } + } +} diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs new file mode 100644 index 000000000..559e1b206 --- /dev/null +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs @@ -0,0 +1,190 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Testing; +using Neo.SmartContract.Testing.TestingStandards; +using Neo.VM; +using System.Numerics; + +namespace Neo.SmartContract.Template.UnitTests.templates.neocontractnep17 +{ + /// + /// You need to build the solution to resolve Nep17Contract class. + /// + [TestClass] + public class Nep17ContractTests : Nep17Tests + { + #region Expected values in base tests + + public override BigInteger ExpectedTotalSupply => 0; + public override string ExpectedSymbol => "EXAMPLE"; + public override byte ExpectedDecimals => 8; + + #endregion + + /// + /// Initialize Test + /// + public Nep17ContractTests() : + base( + "templates/neocontractnep17/Artifacts/Nep17Contract.nef", + "templates/neocontractnep17/Artifacts/Nep17Contract.manifest.json" + ) + { } + + [TestMethod] + public void TestMyMethod() + { + Assert.AreEqual("World", Contract.MyMethod()); + } + + [TestMethod] + public override void TestTransfer() + { + Engine.SetTransactionSigners(Alice); + + // Test mint + + Assert.AreEqual(0, Contract.TotalSupply); + + // Alice is the owner + + Engine.SetTransactionSigners(Alice); + + Contract.Mint(Alice.Account, 10); + + Assert.AreEqual(10, Contract.BalanceOf(Alice.Account)); + Assert.AreEqual(10, Contract.TotalSupply); + AssertTransferEvent(null, Alice.Account, 10); + + // Transfer is done between alice balance to bob + + base.TestTransfer(); + + // Test Burn + + Engine.SetTransactionSigners(Alice); + + Contract.Burn(Alice.Account, Contract.BalanceOf(Alice.Account)); + Contract.Burn(Bob.Account, Contract.BalanceOf(Bob.Account)); + + Assert.AreEqual(0, Contract.TotalSupply); + } + + [TestMethod] + public void TestMintAndBurn() + { + // Alice is the owner + + Engine.SetTransactionSigners(Alice); + + // Test mint -1 + + Assert.ThrowsException(() => Contract.Mint(Alice.Account, -1)); + + // Test mint 0 + + Contract.Mint(Alice.Account, 0); + + Assert.AreEqual(0, Contract.BalanceOf(Alice.Account)); + Assert.AreEqual(0, Contract.TotalSupply); + AssertNoTransferEvent(); + + // test mint + + Contract.Mint(Alice.Account, 10); + + Assert.AreEqual(10, Contract.BalanceOf(Alice.Account)); + Assert.AreEqual(10, Contract.TotalSupply); + AssertTransferEvent(null, Alice.Account, 10); + + // Test burn -1 + + Assert.ThrowsException(() => Contract.Burn(Alice.Account, -1)); + + // Test burn 0 + + Contract.Burn(Alice.Account, 0); + + Assert.AreEqual(10, Contract.BalanceOf(Alice.Account)); + Assert.AreEqual(10, Contract.TotalSupply); + AssertNoTransferEvent(); + + // Test burn + + Contract.Burn(Alice.Account, 10); + + Assert.AreEqual(0, Contract.BalanceOf(Alice.Account)); + Assert.AreEqual(0, Contract.TotalSupply); + AssertTransferEvent(Alice.Account, null, 10); + + // Can't burn more than the BalanceOf + + Assert.ThrowsException(() => Contract.Burn(Alice.Account, 1)); + Assert.ThrowsException(() => Contract.Burn(Bob.Account, 1)); + + // Now check with Bob + + Engine.SetTransactionSigners(Bob); + Assert.ThrowsException(() => Contract.Mint(Alice.Account, 10)); + Assert.ThrowsException(() => Contract.Burn(Alice.Account, 10)); + + // Clean + + Assert.AreEqual(0, Contract.TotalSupply); + } + + [TestMethod] + public void TestUpdate() + { + // Alice is the deployer + + Engine.SetTransactionSigners(Bob); + + Assert.ThrowsException(() => Contract.Update(NefFile, Manifest)); + + Engine.SetTransactionSigners(Alice); + + // Test Update with the same script + + Contract.Update(NefFile, Manifest); + + // Ensure that it works with the same script + + TestTotalSupply(); + } + + [TestMethod] + public void TestDeployWithOwner() + { + // Alice is the deployer + + Engine.SetTransactionSigners(Bob); + + // Test SetOwner notification + + UInt160? previousOwnerRaised = null; + UInt160? newOwnerRaised = null; + + var expectedHash = Engine.GetDeployHash(NefFile, Manifest); + var check = Engine.FromHash(expectedHash, false); + check.OnSetOwner += (previous, newOwner) => + { + previousOwnerRaised = previous; + newOwnerRaised = newOwner; + }; + + // Deploy with random owner, we can use the same storage + // because the contract hash contains the Sender, and now it's random + + var rand = TestEngine.GetNewSigner().Account; + var nep17 = Engine.Deploy(NefFile, Manifest, rand); + Assert.AreEqual(check.Hash, nep17.Hash); + + Coverage?.Join(nep17.GetCoverage()); + + Assert.AreEqual(rand, nep17.Owner); + Assert.IsNull(previousOwnerRaised); + Assert.AreEqual(newOwnerRaised, nep17.Owner); + Assert.AreEqual(newOwnerRaised, rand); + } + } +} diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/OwnerContractTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/OwnerContractTests.cs new file mode 100644 index 000000000..f69001b95 --- /dev/null +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/OwnerContractTests.cs @@ -0,0 +1,23 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Testing; +using Neo.SmartContract.Testing.TestingStandards; + +namespace Neo.SmartContract.Template.UnitTests.templates.neocontractnep17 +{ + /// + /// You need to build the solution to resolve Nep17Contract class. + /// + [TestClass] + public class OwnerContractTests : OwnableTests + { + /// + /// Initialize Test + /// + public OwnerContractTests() : + base( + "templates/neocontractnep17/Artifacts/Nep17Contract.nef", + "templates/neocontractnep17/Artifacts/Nep17Contract.manifest.json" + ) + { } + } +} diff --git a/tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs index 1c1ca6ca2..4f6296337 100644 --- a/tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs +++ b/tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs @@ -18,28 +18,30 @@ public void TestDump() Assert.AreEqual(100_000_000, engine.Native.NEO.TotalSupply); Assert.AreEqual(@" -| 0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5 | 5.26% | -| balanceOf,1 | 0.00% | -| decimals,0 | 0.00% | -| getAccountState,1 | 0.00% | -| getAllCandidates,0 | 0.00% | -| getCandidateVote,1 | 0.00% | -| getCandidates,0 | 0.00% | -| getCommittee,0 | 0.00% | -| getGasPerBlock,0 | 0.00% | -| getNextBlockValidators,0 | 0.00% | -| getRegisterPrice,0 | 0.00% | -| registerCandidate,1 | 0.00% | -| setGasPerBlock,1 | 0.00% | -| setRegisterPrice,1 | 0.00% | -| symbol,0 | 0.00% | -| totalSupply,0 | 100.00% | -| transfer,4 | 0.00% | -| unclaimedGas,2 | 0.00% | -| unregisterCandidate,1 | 0.00% | -| vote,2 | 0.00% | - - +0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5 [5.26%] +┌-────────────────────────-┬-───────-┐ +│ Method │ Line │ +├-────────────────────────-┼-───────-┤ +│ totalSupply,0 │ 100.00% │ +│ balanceOf,1 │ 0.00% │ +│ decimals,0 │ 0.00% │ +│ getAccountState,1 │ 0.00% │ +│ getAllCandidates,0 │ 0.00% │ +│ getCandidates,0 │ 0.00% │ +│ getCandidateVote,1 │ 0.00% │ +│ getCommittee,0 │ 0.00% │ +│ getGasPerBlock,0 │ 0.00% │ +│ getNextBlockValidators,0 │ 0.00% │ +│ getRegisterPrice,0 │ 0.00% │ +│ registerCandidate,1 │ 0.00% │ +│ setGasPerBlock,1 │ 0.00% │ +│ setRegisterPrice,1 │ 0.00% │ +│ symbol,0 │ 0.00% │ +│ transfer,4 │ 0.00% │ +│ unclaimedGas,2 │ 0.00% │ +│ unregisterCandidate,1 │ 0.00% │ +│ vote,2 │ 0.00% │ +└-────────────────────────-┴-───────-┘ ".Trim(), engine.GetCoverage(engine.Native.NEO)?.Dump().Trim()); } @@ -59,7 +61,7 @@ public void TestCoverageByEngine() // Check totalSupply - Assert.IsNull(engine.GetCoverage(engine.Native.NEO)); + Assert.IsNotNull(engine.GetCoverage(engine.Native.NEO)); Assert.AreEqual(100_000_000, engine.Native.NEO.TotalSupply); Assert.AreEqual(engine.Native.NEO.Hash, engine.GetCoverage(engine.Native.NEO)?.Hash); @@ -78,7 +80,7 @@ public void TestCoverageByEngine() // Check coverage by method and expression var methodCovered = engine.GetCoverage(engine.Native.Oracle, o => o.Finish()); - Assert.IsNull(methodCovered); + Assert.IsNotNull(methodCovered); methodCovered = engine.GetCoverage(engine.Native.NEO, o => o.TotalSupply); Assert.AreEqual(3, methodCovered?.TotalInstructions); @@ -99,7 +101,7 @@ public void TestCoverageByEngine() // Check coverage by raw method methodCovered = engine.GetCoverage(engine.Native.Oracle, "finish", 0); - Assert.IsNull(methodCovered); + Assert.IsNotNull(methodCovered); methodCovered = engine.GetCoverage(engine.Native.NEO, "totalSupply", 0); Assert.AreEqual(3, methodCovered?.TotalInstructions); @@ -130,7 +132,7 @@ public void TestCoverageByExtension() // Check totalSupply - Assert.IsNull(engine.Native.NEO.GetCoverage()); + Assert.IsNotNull(engine.Native.NEO.GetCoverage()); Assert.AreEqual(100_000_000, engine.Native.NEO.TotalSupply); Assert.AreEqual(engine.Native.NEO.Hash, engine.Native.NEO.GetCoverage()?.Hash); @@ -149,7 +151,7 @@ public void TestCoverageByExtension() // Check coverage by method and expression var methodCovered = engine.Native.Oracle.GetCoverage(o => o.Finish()); - Assert.IsNull(methodCovered); + Assert.IsNotNull(methodCovered); methodCovered = engine.Native.NEO.GetCoverage(o => o.TotalSupply); Assert.AreEqual(3, methodCovered?.TotalInstructions); @@ -166,7 +168,7 @@ public void TestCoverageByExtension() // Check coverage by raw method methodCovered = engine.GetCoverage(engine.Native.Oracle, "finish", 0); - Assert.IsNull(methodCovered); + Assert.IsNotNull(methodCovered); methodCovered = engine.GetCoverage(engine.Native.NEO, "totalSupply", 0); Assert.AreEqual(3, methodCovered?.TotalInstructions); diff --git a/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs index 66fa2633c..b3c0cfef1 100644 --- a/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs +++ b/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs @@ -16,7 +16,7 @@ public void TestGetArtifactsSource() // Create artifacts - var source = manifest.Abi.GetArtifactsSource(manifest.Name, generateProperties: true); + var source = manifest.GetArtifactsSource(manifest.Name, generateProperties: true); Assert.AreEqual(source, @" using Neo.Cryptography.ECC; @@ -26,69 +26,109 @@ public void TestGetArtifactsSource() namespace Neo.SmartContract.Testing; -public abstract class Contract1 : Neo.SmartContract.Testing.SmartContract +public abstract class Contract1 : Neo.SmartContract.Testing.SmartContract, Neo.SmartContract.Testing.TestingStandards.INep17Standard, Neo.SmartContract.Testing.TestingStandards.IVerificable { #region Events - public delegate void delSetOwner(UInt160 newOwner); + + public delegate void delSetOwner(UInt160? newOwner); + [DisplayName(""SetOwner"")] public event delSetOwner? OnSetOwner; - public delegate void delTransfer(UInt160 from, UInt160 to, BigInteger amount); + [DisplayName(""Transfer"")] - public event delTransfer? OnTransfer; + public event Neo.SmartContract.Testing.TestingStandards.INep17Standard.delTransfer? OnTransfer; + #endregion + #region Properties - public abstract BigInteger Decimals { [DisplayName(""decimals"")] get; } - public abstract UInt160 Owner { [DisplayName(""getOwner"")] get; [DisplayName(""setOwner"")] set; } - public abstract string Symbol { [DisplayName(""symbol"")] get; } - public abstract BigInteger TotalSupply { [DisplayName(""totalSupply"")] get; } - public abstract bool Verify { [DisplayName(""verify"")] get; } + + /// + /// Safe property + /// + public abstract BigInteger? Decimals { [DisplayName(""decimals"")] get; } + + /// + /// Safe property + /// + public abstract UInt160? Owner { [DisplayName(""getOwner"")] get; [DisplayName(""setOwner"")] set; } + + /// + /// Safe property + /// + public abstract string? Symbol { [DisplayName(""symbol"")] get; } + + /// + /// Safe property + /// + public abstract BigInteger? TotalSupply { [DisplayName(""totalSupply"")] get; } + + /// + /// Safe property + /// + public abstract bool? Verify { [DisplayName(""verify"")] get; } + #endregion + #region Safe methods + /// /// Safe method /// [DisplayName(""balanceOf"")] - public abstract BigInteger BalanceOf(UInt160 owner); + public abstract BigInteger? BalanceOf(UInt160? owner); + #endregion + #region Unsafe methods + /// /// Unsafe method /// [DisplayName(""burn"")] - public abstract void Burn(UInt160 account, BigInteger amount); + public abstract void Burn(UInt160? account, BigInteger? amount); + /// /// Unsafe method /// [DisplayName(""mint"")] - public abstract void Mint(UInt160 to, BigInteger amount); + public abstract void Mint(UInt160? to, BigInteger? amount); + /// /// Unsafe method /// [DisplayName(""myMethod"")] - public abstract string MyMethod(); + public abstract string? MyMethod(); + /// /// Unsafe method /// [DisplayName(""onNEP17Payment"")] - public abstract void OnNEP17Payment(UInt160 from, BigInteger amount, object? data = null); + public abstract void OnNEP17Payment(UInt160? from, BigInteger? amount, object? data = null); + /// /// Unsafe method /// [DisplayName(""transfer"")] - public abstract bool Transfer(UInt160 from, UInt160 to, BigInteger amount, object? data = null); + public abstract bool? Transfer(UInt160? from, UInt160? to, BigInteger? amount, object? data = null); + /// /// Unsafe method /// [DisplayName(""update"")] - public abstract void Update(byte[] nefFile, string manifest); + public abstract void Update(byte[]? nefFile, string? manifest); + /// /// Unsafe method /// [DisplayName(""withdraw"")] - public abstract bool Withdraw(UInt160 token, UInt160 to, BigInteger amount); + public abstract bool? Withdraw(UInt160? token, UInt160? to, BigInteger? amount); + #endregion + #region Constructor for internal use only + protected Contract1(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) { } + #endregion } ".Replace("\r\n", "\n").TrimStart()); diff --git a/tests/Neo.SmartContract.Testing.UnitTests/Extensions/TestExtensionsTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/Extensions/TestExtensionsTests.cs new file mode 100644 index 000000000..b909d3f38 --- /dev/null +++ b/tests/Neo.SmartContract.Testing.UnitTests/Extensions/TestExtensionsTests.cs @@ -0,0 +1,19 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Testing.Extensions; +using Neo.VM; +using Neo.VM.Types; + +namespace Neo.SmartContract.TestEngine.UnitTests.Extensions +{ + [TestClass] + public class TestExtensionsTests + { + [TestMethod] + public void TestConvertEnum() + { + StackItem stackItem = new Integer((int)VMState.FAULT); + + Assert.AreEqual(VMState.FAULT, (VMState)stackItem.ConvertTo(typeof(VMState))); + } + } +} diff --git a/tests/Neo.SmartContract.Testing.UnitTests/NativeArtifactsTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/NativeArtifactsTests.cs index 949391761..ce45fa1bb 100644 --- a/tests/Neo.SmartContract.Testing.UnitTests/NativeArtifactsTests.cs +++ b/tests/Neo.SmartContract.Testing.UnitTests/NativeArtifactsTests.cs @@ -34,9 +34,9 @@ public void TestInitialize() // Check coverage - Assert.AreEqual(100.0F, engine.Native.NEO.GetCoverage(o => o.Symbol).CoveredPercentage); - Assert.AreEqual(100.0F, engine.Native.NEO.GetCoverage(o => o.TotalSupply).CoveredPercentage); - Assert.AreEqual(100.0F, engine.Native.NEO.GetCoverage(o => o.BalanceOf(It.IsAny())).CoveredPercentage); + Assert.AreEqual(1F, engine.Native.NEO.GetCoverage(o => o.Symbol).CoveredPercentage); + Assert.AreEqual(1F, engine.Native.NEO.GetCoverage(o => o.TotalSupply).CoveredPercentage); + Assert.AreEqual(1F, engine.Native.NEO.GetCoverage(o => o.BalanceOf(It.IsAny())).CoveredPercentage); } [TestMethod] diff --git a/tests/Neo.SmartContract.Testing.UnitTests/TestEngineTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/TestEngineTests.cs index dbe763fcf..728f4ab0d 100644 --- a/tests/Neo.SmartContract.Testing.UnitTests/TestEngineTests.cs +++ b/tests/Neo.SmartContract.Testing.UnitTests/TestEngineTests.cs @@ -22,7 +22,7 @@ public void GenerateNativeArtifacts() foreach (var n in Native.NativeContract.Contracts) { var manifest = n.Manifest; - var source = manifest.Abi.GetArtifactsSource(manifest.Name, generateProperties: true); + var source = manifest.GetArtifactsSource(manifest.Name, generateProperties: true); var fullPath = Path.GetFullPath($"../../../../../src/Neo.SmartContract.Testing/Native/{manifest.Name}.cs"); File.WriteAllText(fullPath, source);