diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 10da41fa0..af1c3a64f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,11 +31,19 @@ jobs: 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 neocontractnep17 -n Nep17Contract -o ./src/Neo.SmartContract.Template/bin/Debug/nep17/ --force + dotnet new neocontractowner -n Ownable -o ./src/Neo.SmartContract.Template/bin/Debug/ownable/ --force + dotnet new neocontractoracle -n OracleRequest -o ./src/Neo.SmartContract.Template/bin/Debug/oracle/ --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 ./src/Neo.SmartContract.Template/bin/Debug/Nep17Contract.csproj -o ./tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Artifacts/ --generate-artifacts source --debug + dotnet remove ./src/Neo.SmartContract.Template/bin/Debug/nep17/Nep17Contract.csproj package Neo.SmartContract.Framework + dotnet add ./src/Neo.SmartContract.Template/bin/Debug/nep17/Nep17Contract.csproj reference ./src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj + dotnet remove ./src/Neo.SmartContract.Template/bin/Debug/ownable/Ownable.csproj package Neo.SmartContract.Framework + dotnet add ./src/Neo.SmartContract.Template/bin/Debug/ownable/Ownable.csproj reference ./src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj + dotnet remove ./src/Neo.SmartContract.Template/bin/Debug/oracle/OracleRequest.csproj package Neo.SmartContract.Framework + dotnet add ./src/Neo.SmartContract.Template/bin/Debug/oracle/OracleRequest.csproj reference ./src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj + dotnet ./src/Neo.Compiler.CSharp/bin/Debug/net7.0/nccs.dll ./src/Neo.SmartContract.Template/bin/Debug/nep17/Nep17Contract.csproj -o ./tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Artifacts/ --generate-artifacts source --debug + dotnet ./src/Neo.Compiler.CSharp/bin/Debug/net7.0/nccs.dll ./src/Neo.SmartContract.Template/bin/Debug/ownable/Ownable.csproj -o ./tests/Neo.SmartContract.Template.UnitTests/templates/neocontractowner/Artifacts/ --generate-artifacts source --debug + dotnet ./src/Neo.Compiler.CSharp/bin/Debug/net7.0/nccs.dll ./src/Neo.SmartContract.Template/bin/Debug/oracle/OracleRequest.csproj -o ./tests/Neo.SmartContract.Template.UnitTests/templates/neocontractoracle/Artifacts/ --generate-artifacts source --debug - name: Build Solution run: dotnet build ./neo-devpack-dotnet.sln - name: Add package coverlet.msbuild diff --git a/src/Neo.Compiler.CSharp/Program.cs b/src/Neo.Compiler.CSharp/Program.cs index 355c4dd6c..c20f0fbeb 100644 --- a/src/Neo.Compiler.CSharp/Program.cs +++ b/src/Neo.Compiler.CSharp/Program.cs @@ -212,7 +212,7 @@ private static int ProcessOutput(Options options, string folder, CompilationCont if (options.GenerateArtifacts != Options.GenerateArtifactsKind.None) { - var artifact = manifest.GetArtifactsSource(baseName); + var artifact = manifest.GetArtifactsSource(baseName, nef, debugInfo); if (options.GenerateArtifacts == Options.GenerateArtifactsKind.All || options.GenerateArtifacts == Options.GenerateArtifactsKind.Source) { diff --git a/src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.cs b/src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.cs index cf4a3b79c..59c9b62b2 100644 --- a/src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.cs +++ b/src/Neo.SmartContract.Template/templates/neocontractnep17/Nep17Contract.cs @@ -8,7 +8,7 @@ using System.ComponentModel; using System.Numerics; -namespace ProjectName +namespace Neo.SmartContract.Template { [DisplayName(nameof(Nep17Contract))] [ManifestExtra("Author", "")] @@ -109,7 +109,7 @@ public static void _deploy(object data, bool update) Storage.Put(Storage.CurrentContext, "Hello", "World"); } - public static void Update(ByteString nefFile, string manifest, object data) + public static void Update(ByteString nefFile, string manifest, object? data = null) { if (IsOwner() == false) throw new InvalidOperationException("No authorization."); diff --git a/src/Neo.SmartContract.Template/templates/neocontractoracle/.template.config/template.json b/src/Neo.SmartContract.Template/templates/neocontractoracle/.template.config/template.json index c94fcd257..af853ee72 100644 --- a/src/Neo.SmartContract.Template/templates/neocontractoracle/.template.config/template.json +++ b/src/Neo.SmartContract.Template/templates/neocontractoracle/.template.config/template.json @@ -10,7 +10,7 @@ "language": "C#", "type": "project" }, - "sourceName": "ProjectName", + "sourceName": "OracleRequest", "symbols": { "NeoVersion": { "type": "parameter", diff --git a/src/Neo.SmartContract.Template/templates/neocontractoracle/Contract1.cs b/src/Neo.SmartContract.Template/templates/neocontractoracle/OracleRequest.cs similarity index 52% rename from src/Neo.SmartContract.Template/templates/neocontractoracle/Contract1.cs rename to src/Neo.SmartContract.Template/templates/neocontractoracle/OracleRequest.cs index 4d74615bc..fb3514484 100644 --- a/src/Neo.SmartContract.Template/templates/neocontractoracle/Contract1.cs +++ b/src/Neo.SmartContract.Template/templates/neocontractoracle/OracleRequest.cs @@ -6,67 +6,26 @@ using Neo.SmartContract.Framework.Services; using System; +using System.Collections.Generic; using System.ComponentModel; -namespace ProjectName +namespace Neo.SmartContract.Template { - [DisplayName(nameof(Contract1))] + [DisplayName(nameof(OracleRequest))] [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/neocontractoracle/OracleRequest.cs")] [ContractPermission("*", "*")] - public class Contract1 : SmartContract + public class OracleRequest : Neo.SmartContract.Framework.SmartContract { - public delegate void OnRequestSuccessfulDelegate(string requestedUrl, object jsonValue); - - [DisplayName("RequestSuccessful")] - public static event OnRequestSuccessfulDelegate OnRequestSuccessful; - - // TODO: Replace it with your own address. - [InitialValue("", ContractParameterType.Hash160)] - static readonly UInt160 Owner = default; - - private static bool IsOwner() => Runtime.CheckWitness(Owner); - - // 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. [Safe] - public static bool Verify() => IsOwner(); - - // TODO: Replace it with your methods. - public static string MyMethod() - { - return Storage.Get(Storage.CurrentContext, "Hello"); - } - - public static void _deploy(object data, bool update) - { - if (update) - { - // This will be executed during update - return; - } - - // This will be executed during deploy - Storage.Put(Storage.CurrentContext, "Hello", "World"); - } - - public static void Update(ByteString nefFile, string manifest) - { - if (!IsOwner()) throw new Exception("No authorization."); - ContractManagement.Update(nefFile, manifest, null); - } - - public static void Destroy() + public static string GetResponse() { - if (!IsOwner()) throw new Exception("No authorization."); - ContractManagement.Destroy(); + return Storage.Get(Storage.CurrentContext, "Response"); } - // TODO: Add your own logic public static void DoRequest() { /* @@ -93,7 +52,7 @@ JSON DATA EXAMPLE } // This method is called after the Oracle receives response from requested URL - public static void OnOracleResponse(string requestedUrl, object userData, OracleResponseCode oracleResponse, string jsonString) + public static void onOracleResponse(string requestedUrl, object userData, OracleResponseCode oracleResponse, string jsonString) { if (Runtime.CallingScriptHash != Oracle.Hash) throw new InvalidOperationException("No Authorization!"); @@ -103,7 +62,7 @@ public static void OnOracleResponse(string requestedUrl, object userData, Oracle var jsonArrayValues = (object[])StdLib.JsonDeserialize(jsonString); var jsonFirstValue = (string)jsonArrayValues[0]; - OnRequestSuccessful(requestedUrl, jsonFirstValue); + Storage.Put(Storage.CurrentContext, "Response", jsonFirstValue); } } } diff --git a/src/Neo.SmartContract.Template/templates/neocontractoracle/ProjectName.csproj b/src/Neo.SmartContract.Template/templates/neocontractoracle/OracleRequest.csproj similarity index 100% rename from src/Neo.SmartContract.Template/templates/neocontractoracle/ProjectName.csproj rename to src/Neo.SmartContract.Template/templates/neocontractoracle/OracleRequest.csproj diff --git a/src/Neo.SmartContract.Template/templates/neocontractowner/.template.config/template.json b/src/Neo.SmartContract.Template/templates/neocontractowner/.template.config/template.json index 55a8ef12c..3c40192de 100644 --- a/src/Neo.SmartContract.Template/templates/neocontractowner/.template.config/template.json +++ b/src/Neo.SmartContract.Template/templates/neocontractowner/.template.config/template.json @@ -10,7 +10,7 @@ "language": "C#", "type": "project" }, - "sourceName": "ProjectName", + "sourceName": "Ownable", "symbols": { "NeoVersion": { "type": "parameter", diff --git a/src/Neo.SmartContract.Template/templates/neocontractowner/Contract1.cs b/src/Neo.SmartContract.Template/templates/neocontractowner/Ownable.cs similarity index 51% rename from src/Neo.SmartContract.Template/templates/neocontractowner/Contract1.cs rename to src/Neo.SmartContract.Template/templates/neocontractowner/Ownable.cs index b6089817e..d4431f25e 100644 --- a/src/Neo.SmartContract.Template/templates/neocontractowner/Contract1.cs +++ b/src/Neo.SmartContract.Template/templates/neocontractowner/Ownable.cs @@ -8,35 +8,48 @@ using System; using System.ComponentModel; -namespace ProjectName +namespace Neo.SmartContract.Template { - [DisplayName(nameof(Contract1))] + [DisplayName(nameof(Ownable))] [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/neocontractowner/Ownable.cs")] [ContractPermission("*", "*")] - public class Contract1 : SmartContract + public class Ownable : Neo.SmartContract.Framework.SmartContract { + #region Owner + private const byte Prefix_Owner = 0xff; - public delegate void OnSetOwnerDelegate(UInt160 newOwner); + [Safe] + public static UInt160 GetOwner() + { + return (UInt160)Storage.Get(new[] { Prefix_Owner }); + } + + private static bool IsOwner() => + Runtime.CheckWitness(GetOwner()); + + public delegate void OnSetOwnerDelegate(UInt160 previousOwner, UInt160 newOwner); [DisplayName("SetOwner")] public static event OnSetOwnerDelegate OnSetOwner; - // TODO: Replace it with your own address. - [InitialValue("", ContractParameterType.Hash160)] - private static readonly UInt160 InitialOwner = default; + public static void SetOwner(UInt160 newOwner) + { + if (IsOwner() == false) + throw new InvalidOperationException("No Authorization!"); - private static bool IsOwner() => Runtime.CheckWitness(GetOwner()); + ExecutionEngine.Assert(newOwner.IsValid && !newOwner.IsZero, "owner must be valid"); - // 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. - [Safe] - public static bool Verify() => IsOwner(); + UInt160 previous = GetOwner(); + Storage.Put(new[] { Prefix_Owner }, newOwner); + OnSetOwner(previous, newOwner); + } + + #endregion // TODO: Replace it with your methods. public static string MyMethod() @@ -44,6 +57,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) @@ -52,43 +66,30 @@ public static void _deploy(object data, bool update) return; } - // This will be executed during deploy + // Init method, you must deploy the contract with the owner as an argument, or it will take the sender + if (data is null) data = Runtime.Transaction.Sender; + + UInt160 initialOwner = (UInt160)data; + + ExecutionEngine.Assert(initialOwner.IsValid && !initialOwner.IsZero, "owner must exists"); + + Storage.Put(new[] { Prefix_Owner }, initialOwner); + 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 = null) { - 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); } public static void Destroy() { - if (!IsOwner()) throw new Exception("No authorization."); + if (!IsOwner()) + throw new InvalidOperationException("No authorization."); ContractManagement.Destroy(); } - - // Safe is for read operations Or Safe to call by everyone - [Safe] - public static UInt160 GetOwner() - { - var currentOwner = Storage.Get(new[] { Prefix_Owner }); - - if (currentOwner == null) - return InitialOwner; - - return (UInt160)currentOwner; - } - - public static void SetOwner(UInt160 newOwner) - { - if (IsOwner() == false) - throw new InvalidOperationException("No Authorization!"); - if (newOwner != null && newOwner.IsValid) - { - Storage.Put(new[] { Prefix_Owner }, newOwner); - OnSetOwner(newOwner); - } - } } } diff --git a/src/Neo.SmartContract.Template/templates/neocontractowner/ProjectName.csproj b/src/Neo.SmartContract.Template/templates/neocontractowner/Ownable.csproj similarity index 100% rename from src/Neo.SmartContract.Template/templates/neocontractowner/ProjectName.csproj rename to src/Neo.SmartContract.Template/templates/neocontractowner/Ownable.csproj diff --git a/src/Neo.SmartContract.Testing/Coverage/CoverageBase.cs b/src/Neo.SmartContract.Testing/Coverage/CoverageBase.cs index b8d9b83d2..3de6a1982 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoverageBase.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoverageBase.cs @@ -93,6 +93,13 @@ public IEnumerable GetCoverageBranchFrom(int offset, int length) } } + /// + /// Dump coverage + /// + /// Format + /// Coverage dump + public abstract string Dump(DumpFormat format = DumpFormat.Console); + public static decimal CalculateHitRate(int total, int hits) => total == 0 ? 1m : new decimal(hits) / new decimal(total); diff --git a/src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs b/src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs index 523f51984..e2d2d59dd 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs @@ -1,4 +1,7 @@ +using Neo.SmartContract.Testing.Coverage.Formats; +using System; using System.Collections.Generic; +using System.Linq; namespace Neo.SmartContract.Testing.Coverage { @@ -51,5 +54,37 @@ public CoveredCollection(params CoverageBase[] entries) { Entries = entries; } + + /// + /// Dump coverage + /// + /// Format + /// Coverage dump + public override string Dump(DumpFormat format = DumpFormat.Console) + { + IEnumerable<(CoveredContract, Func?)> entries = Entries.Select(u => + { + if (u is CoveredContract co) return (co, (Func?)null); + if (u is CoveredMethod cm) return (cm.Contract, new Func((CoveredMethod method) => ReferenceEquals(method, cm))); + + throw new NotImplementedException(); + })!; + + switch (format) + { + case DumpFormat.Console: + { + return new ConsoleFormat(entries).Dump(); + } + case DumpFormat.Html: + { + return new IntructionHtmlFormat(entries).Dump(); + } + default: + { + throw new NotImplementedException(); + } + } + } } } diff --git a/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs b/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs index 7eddb8f80..d457fc8ac 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs @@ -5,13 +5,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; -using System.Text; namespace Neo.SmartContract.Testing.Coverage { - [DebuggerDisplay("{Hash.ToString()}")] + [DebuggerDisplay("{Name}")] public class CoveredContract : CoverageBase { #region Internal @@ -21,6 +19,11 @@ public class CoveredContract : CoverageBase #endregion + /// + /// Contract name + /// + public string Name { get; private set; } + /// /// Contract Hash /// @@ -54,13 +57,21 @@ public CoveredContract(MethodDetectionMechanism mechanism, UInt160 hash, Contrac if (state is not null) { + Name = state.Manifest.Name + $" [{Hash}]"; + // Extract all methods GenerateMethods(mechanism, state); } + else + { + Name = Hash.ToString(); + } } internal void GenerateMethods(MethodDetectionMechanism mechanism, ContractState state) { + Name = state.Manifest.Name + $" [{Hash}]"; + Script script = state.Script; HashSet privateAdded = new(); List methods = new(state.Manifest.Abi.Methods); @@ -277,27 +288,19 @@ public void Join(CoverageBase? coverage) /// /// Dump coverage /// + /// Format /// Coverage dump - public string Dump(DumpFormat format = DumpFormat.Console) - { - return Dump(format, Methods); - } - - /// - /// Dump coverage - /// - /// Coverage dump - internal string Dump(DumpFormat format, params CoveredMethod[] methods) + public override string Dump(DumpFormat format = DumpFormat.Console) { switch (format) { case DumpFormat.Console: { - return Dump(new ConsoleFormat(this, methods)); + return new ConsoleFormat(this).Dump(); } case DumpFormat.Html: { - return Dump(new IntructionHtmlFormat(this, methods)); + return new IntructionHtmlFormat(this).Dump(); } default: { @@ -306,28 +309,6 @@ internal string Dump(DumpFormat format, params CoveredMethod[] methods) } } - /// - /// Dump to format - /// - /// Format - /// Debug Info - /// Covertura - public string Dump(ICoverageFormat format) - { - Dictionary outputMap = new(); - - void writeAttachment(string filename, Action writestream) - { - using MemoryStream stream = new(); - writestream(stream); - var text = Encoding.UTF8.GetString(stream.ToArray()); - outputMap.Add(filename, text); - } - - format.WriteReport(writeAttachment); - return outputMap.First().Value; - } - /// /// Hit /// diff --git a/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs b/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs index 05b99c4dc..da207397a 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs @@ -1,4 +1,6 @@ using Neo.SmartContract.Manifest; +using Neo.SmartContract.Testing.Coverage.Formats; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -55,8 +57,26 @@ public CoveredMethod(CoveredContract contract, ContractMethodDescriptor method, /// /// Dump coverage /// + /// Format /// Coverage dump - public string Dump(DumpFormat format = DumpFormat.Console) => Contract.Dump(format, this); + public override string Dump(DumpFormat format = DumpFormat.Console) + { + switch (format) + { + case DumpFormat.Console: + { + return new ConsoleFormat(Contract, m => ReferenceEquals(m, this)).Dump(); + } + case DumpFormat.Html: + { + return new IntructionHtmlFormat(Contract, m => ReferenceEquals(m, this)).Dump(); + } + default: + { + throw new NotImplementedException(); + } + } + } public override string ToString() => Method.ToString(); } diff --git a/src/Neo.SmartContract.Testing/Coverage/Formats/CoberturaFormat.cs b/src/Neo.SmartContract.Testing/Coverage/Formats/CoberturaFormat.cs index 7fbc668c9..68e02273d 100644 --- a/src/Neo.SmartContract.Testing/Coverage/Formats/CoberturaFormat.cs +++ b/src/Neo.SmartContract.Testing/Coverage/Formats/CoberturaFormat.cs @@ -1,4 +1,3 @@ -using Neo.VM; using System; using System.Collections.Generic; using System.IO; @@ -7,7 +6,7 @@ namespace Neo.SmartContract.Testing.Coverage.Formats { - public partial class CoberturaFormat : ICoverageFormat + public partial class CoberturaFormat : CoverageFormatBase { /// /// Contract @@ -23,7 +22,7 @@ public CoberturaFormat(params (CoveredContract Contract, NeoDebugInfo DebugInfo) Contracts = contracts; } - public void WriteReport(Action> writeAttachement) + public override void WriteReport(Action> writeAttachement) { writeAttachement("coverage.cobertura.xml", stream => { diff --git a/src/Neo.SmartContract.Testing/Coverage/Formats/ConsoleFormat.cs b/src/Neo.SmartContract.Testing/Coverage/Formats/ConsoleFormat.cs index 426351c27..9ed775556 100644 --- a/src/Neo.SmartContract.Testing/Coverage/Formats/ConsoleFormat.cs +++ b/src/Neo.SmartContract.Testing/Coverage/Formats/ConsoleFormat.cs @@ -5,30 +5,33 @@ namespace Neo.SmartContract.Testing.Coverage.Formats { - public partial class ConsoleFormat : ICoverageFormat + public partial class ConsoleFormat : CoverageFormatBase { /// - /// Contract + /// Entries /// - public CoveredContract Contract { get; } + public (CoveredContract Contract, Func? Filter)[] Entries { get; } /// - /// Selective methods + /// Constructor /// - public CoveredMethod[] Methods { get; } + /// Contract + /// Method Filter + public ConsoleFormat(CoveredContract contract, Func? Filter = null) + { + Entries = new (CoveredContract, Func?)[] { (contract, Filter) }; + } /// /// Constructor /// - /// Contract - /// Methods - public ConsoleFormat(CoveredContract contract, params CoveredMethod[] methods) + /// Entries + public ConsoleFormat(IEnumerable<(CoveredContract, Func?)> entries) { - Contract = contract; - Methods = methods; + Entries = entries.ToArray(); } - public void WriteReport(Action> writeAttachement) + public override void WriteReport(Action> writeAttachement) { writeAttachement("coverage.cobertura.txt", stream => { @@ -43,34 +46,40 @@ public void WriteReport(Action> writeAttachement) private void WriteReport(StreamWriter writer) { - var coverLines = $"{Contract.CoveredLinesPercentage:P2}"; - var coverBranch = $"{Contract.CoveredBranchPercentage:P2}"; - writer.WriteLine($"{Contract.Hash} [{coverLines} - {coverBranch}]"); + foreach ((var contract, var methods) in Entries) + { + var coverLines = $"{contract.CoveredLinesPercentage:P2}"; + var coverBranch = $"{contract.CoveredBranchPercentage:P2}"; + writer.WriteLine($"{contract.Name} [{coverLines} - {coverBranch}]"); - List rows = new(); - var max = new int[] { "Method".Length, "Line ".Length, "Branch".Length }; + List rows = new(); + var max = new int[] { "Method".Length, "Line ".Length, "Branch".Length }; - foreach (var method in Methods.OrderBy(u => u.Method.Name).OrderByDescending(u => u.CoveredLinesPercentage)) - { - coverLines = $"{method.CoveredLinesPercentage:P2}"; - coverBranch = $"{method.CoveredBranchPercentage:P2}"; - rows.Add(new string[] { method.Method.ToString(), coverLines, coverBranch }); + foreach (var method in contract.Methods + .Where(u => methods is null || methods.Invoke(u)) + .OrderBy(u => u.Method.Name) + .OrderByDescending(u => u.CoveredLinesPercentage)) + { + coverLines = $"{method.CoveredLinesPercentage:P2}"; + coverBranch = $"{method.CoveredBranchPercentage:P2}"; + rows.Add(new string[] { method.Method.ToString(), coverLines, coverBranch }); - max[0] = Math.Max(method.Method.ToString().Length, max[0]); - max[1] = Math.Max(coverLines.Length, max[1]); - max[2] = Math.Max(coverLines.Length, max[2]); - } + max[0] = Math.Max(method.Method.ToString().Length, max[0]); + max[1] = Math.Max(coverLines.Length, max[1]); + max[2] = Math.Max(coverLines.Length, max[2]); + } - writer.WriteLine($"┌-{"─".PadLeft(max[0], '─')}-┬-{"─".PadLeft(max[1], '─')}-┬-{"─".PadLeft(max[1], '─')}-┐"); - writer.WriteLine($"│ {string.Format($"{{0,-{max[0]}}}", "Method", max[0])} │ {string.Format($"{{0,{max[1]}}}", "Line ", max[1])} │ {string.Format($"{{0,{max[2]}}}", "Branch", max[1])} │"); - writer.WriteLine($"├-{"─".PadLeft(max[0], '─')}-┼-{"─".PadLeft(max[1], '─')}-┼-{"─".PadLeft(max[1], '─')}-┤"); + writer.WriteLine($"┌-{"─".PadLeft(max[0], '─')}-┬-{"─".PadLeft(max[1], '─')}-┬-{"─".PadLeft(max[1], '─')}-┐"); + writer.WriteLine($"│ {string.Format($"{{0,-{max[0]}}}", "Method", max[0])} │ {string.Format($"{{0,{max[1]}}}", "Line ", max[1])} │ {string.Format($"{{0,{max[2]}}}", "Branch", max[1])} │"); + writer.WriteLine($"├-{"─".PadLeft(max[0], '─')}-┼-{"─".PadLeft(max[1], '─')}-┼-{"─".PadLeft(max[1], '─')}-┤"); - foreach (var print in rows) - { - writer.WriteLine($"│ {string.Format($"{{0,-{max[0]}}}", print[0], max[0])} │ {string.Format($"{{0,{max[1]}}}", print[1], max[1])} │ {string.Format($"{{0,{max[1]}}}", print[2], max[2])} │"); - } + foreach (var print in rows) + { + writer.WriteLine($"│ {string.Format($"{{0,-{max[0]}}}", print[0], max[0])} │ {string.Format($"{{0,{max[1]}}}", print[1], max[1])} │ {string.Format($"{{0,{max[1]}}}", print[2], max[2])} │"); + } - writer.WriteLine($"└-{"─".PadLeft(max[0], '─')}-┴-{"─".PadLeft(max[1], '─')}-┴-{"─".PadLeft(max[2], '─')}-┘"); + writer.WriteLine($"└-{"─".PadLeft(max[0], '─')}-┴-{"─".PadLeft(max[1], '─')}-┴-{"─".PadLeft(max[2], '─')}-┘"); + } } } } diff --git a/src/Neo.SmartContract.Testing/Coverage/Formats/CoverageFormatBase.cs b/src/Neo.SmartContract.Testing/Coverage/Formats/CoverageFormatBase.cs new file mode 100644 index 000000000..659c6c8b7 --- /dev/null +++ b/src/Neo.SmartContract.Testing/Coverage/Formats/CoverageFormatBase.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Neo.SmartContract.Testing.Coverage.Formats +{ + public abstract class CoverageFormatBase + { + public abstract void WriteReport(Action> writeAttachement); + + /// + /// Dump to format + /// + /// First entry as string + public string Dump() + { + Dictionary outputMap = new(); + + void writeAttachment(string filename, Action writestream) + { + using MemoryStream stream = new(); + writestream(stream); + var text = Encoding.UTF8.GetString(stream.ToArray()); + outputMap.Add(filename, text); + } + + WriteReport(writeAttachment); + return outputMap.First().Value; + } + } +} diff --git a/src/Neo.SmartContract.Testing/Coverage/Formats/ICoverageFormat.cs b/src/Neo.SmartContract.Testing/Coverage/Formats/ICoverageFormat.cs deleted file mode 100644 index 056273e57..000000000 --- a/src/Neo.SmartContract.Testing/Coverage/Formats/ICoverageFormat.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Neo.SmartContract.Testing.Coverage.Formats -{ - public interface ICoverageFormat - { - void WriteReport(Action> writeAttachement); - } -} diff --git a/src/Neo.SmartContract.Testing/Coverage/Formats/IntructionHtmlFormat.cs b/src/Neo.SmartContract.Testing/Coverage/Formats/IntructionHtmlFormat.cs index 25d0101ec..ac00d450e 100644 --- a/src/Neo.SmartContract.Testing/Coverage/Formats/IntructionHtmlFormat.cs +++ b/src/Neo.SmartContract.Testing/Coverage/Formats/IntructionHtmlFormat.cs @@ -1,34 +1,37 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; namespace Neo.SmartContract.Testing.Coverage.Formats { - public partial class IntructionHtmlFormat : ICoverageFormat + public partial class IntructionHtmlFormat : CoverageFormatBase { /// - /// Contract + /// Entries /// - public CoveredContract Contract { get; } + public (CoveredContract Contract, Func? Filter)[] Entries { get; } /// - /// Selective methods + /// Constructor /// - public CoveredMethod[] Methods { get; } + /// Contract + /// Method Filter + public IntructionHtmlFormat(CoveredContract contract, Func? filter = null) + { + Entries = new (CoveredContract, Func?)[] { (contract, filter) }; + } /// /// Constructor /// - /// Contract - /// Methods - public IntructionHtmlFormat(CoveredContract contract, params CoveredMethod[] methods) + /// Entries + public IntructionHtmlFormat(IEnumerable<(CoveredContract, Func?)> entries) { - Contract = contract; - Methods = methods; + Entries = entries.ToArray(); } - public void WriteReport(Action> writeAttachement) + public override void WriteReport(Action> writeAttachement) { writeAttachement("coverage.cobertura.html", stream => { @@ -73,23 +76,28 @@ private void WriteReport(StreamWriter writer) "); - writer.WriteLine($@" + foreach ((var contract, var methods) in Entries) + { + writer.WriteLine($@"
-
{Contract.Hash}
-
 {Contract.CoveredBranchPercentage:P2} 
-
 {Contract.CoveredLinesPercentage:P2} 
+
{contract.Name}
+
 {contract.CoveredBranchPercentage:P2} 
+
 {contract.CoveredLinesPercentage:P2} 
"); - foreach (var method in Methods.OrderBy(u => u.Method.Name).OrderByDescending(u => u.CoveredLinesPercentage)) - { - var kind = "low"; - if (method.CoveredLinesPercentage > 0.7M) kind = "medium"; - if (method.CoveredLinesPercentage > 0.8M) kind = "high"; + foreach (var method in contract.Methods + .Where(u => methods is null || methods.Invoke(u)) + .OrderBy(u => u.Method.Name) + .OrderByDescending(u => u.CoveredLinesPercentage)) + { + var kind = "low"; + if (method.CoveredLinesPercentage > 0.7M) kind = "medium"; + if (method.CoveredLinesPercentage > 0.8M) kind = "high"; - writer.WriteLine($@" + writer.WriteLine($@"
{method.Method}
 {method.CoveredBranchPercentage:P2} 
@@ -97,28 +105,29 @@ private void WriteReport(StreamWriter writer)
"); - writer.WriteLine($@"
"); - - foreach (var hit in method.Lines) - { - var noHit = hit.Hits == 0 ? "no-" : ""; - var icon = hit.Hits == 0 ? "✘" : "✔"; - var branch = ""; + writer.WriteLine($@"
"); - if (Contract.TryGetBranch(hit.Offset, out var b)) + foreach (var hit in method.Lines) { - branch = $" [ᛦ {b.Hits}/{b.Count}]"; + var noHit = hit.Hits == 0 ? "no-" : ""; + var icon = hit.Hits == 0 ? "✘" : "✔"; + var branch = ""; + + if (contract.TryGetBranch(hit.Offset, out var b)) + { + branch = $" [ᛦ {b.Hits}/{b.Count}]"; + } + + writer.WriteLine($@"
{icon}{hit.Hits} Hits{hit.Description}{branch}
"); } - writer.WriteLine($@"
{icon}{hit.Hits} Hits{hit.Description}{branch}
"); + writer.WriteLine($@"
"); } - writer.WriteLine($@"
-"); + writer.WriteLine($@"
"); } writer.WriteLine(@" - - "); diff --git a/src/Neo.SmartContract.Testing/Coverage/NeoDebugInfo.cs b/src/Neo.SmartContract.Testing/Coverage/NeoDebugInfo.cs index fc7bd110e..6f7b424f9 100644 --- a/src/Neo.SmartContract.Testing/Coverage/NeoDebugInfo.cs +++ b/src/Neo.SmartContract.Testing/Coverage/NeoDebugInfo.cs @@ -142,6 +142,15 @@ internal static NeoDebugInfo Load(Stream stream) return FromDebugInfoJson(jo); } + public static NeoDebugInfo FromDebugInfoJson(string json) + { + var jsonToken = JToken.Parse(json); + if (jsonToken is not JObject jobj) + throw new FormatException("The json must be an object"); + + return FromDebugInfoJson(jobj); + } + public static NeoDebugInfo FromDebugInfoJson(JObject json) { if (json["hash"]?.GetString() is not string sHash) diff --git a/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs b/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs index 27e3f6251..212522c82 100644 --- a/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs +++ b/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs @@ -1,4 +1,7 @@ +using Neo.IO; +using Neo.Json; using Neo.SmartContract.Manifest; +using Neo.SmartContract.Testing.Coverage; using Neo.SmartContract.Testing.TestingStandards; using System; using System.Collections.Generic; @@ -32,9 +35,11 @@ public static class ArtifactExtensions ///
/// Manifest /// Class name, by default is manifest.Name + /// Nef file + /// Debug Info /// Generate properties /// Source - public static string GetArtifactsSource(this ContractManifest manifest, string? name = null, bool generateProperties = true) + public static string GetArtifactsSource(this ContractManifest manifest, string? name = null, NefFile? nef = null, JToken? debugInfo = null, bool generateProperties = true) { name ??= manifest.Name; @@ -63,6 +68,32 @@ public static string GetArtifactsSource(this ContractManifest manifest, string? sourceCode.WriteLine($"public abstract class {name} : " + string.Join(", ", inheritance)); sourceCode.WriteLine("{"); + // Write compiled data + + sourceCode.WriteLine(" #region Compiled data"); + sourceCode.WriteLine(); + + var value = manifest.ToJson().ToString(false).Replace("\"", "\"\""); + sourceCode.WriteLine($" public static readonly {typeof(ContractManifest).FullName} Manifest = {typeof(ContractManifest).FullName}.Parse(@\"{value}\");"); + sourceCode.WriteLine(); + + if (nef is not null) + { + value = Convert.ToBase64String(nef.ToArray()).Replace("\"", "\"\""); + sourceCode.WriteLine($" public static readonly {typeof(NefFile).FullName} Nef = Neo.IO.Helper.AsSerializable<{typeof(NefFile).FullName}>(Convert.FromBase64String(@\"{value}\"));"); + sourceCode.WriteLine(); + } + + if (debugInfo is not null) + { + value = debugInfo.ToString(false).Replace("\"", "\"\""); + sourceCode.WriteLine($" public static readonly {typeof(NeoDebugInfo).FullName} DebugInfo = {typeof(NeoDebugInfo).FullName}.FromDebugInfoJson(@\"{value}\");"); + sourceCode.WriteLine(); + } + + sourceCode.WriteLine(" #endregion"); + sourceCode.WriteLine(); + // Crete events if (manifest.Abi.Events.Any()) @@ -145,7 +176,7 @@ public static string GetArtifactsSource(this ContractManifest manifest, string? sourceCode.WriteLine(" #region Constructor for internal use only"); sourceCode.WriteLine(); - sourceCode.WriteLine($" protected {name}(Neo.SmartContract.Testing.SmartContractInitialize initialize) : base(initialize) {{ }}"); + sourceCode.WriteLine($" protected {name}({typeof(SmartContractInitialize).FullName} initialize) : base(initialize) {{ }}"); sourceCode.WriteLine(); sourceCode.WriteLine(" #endregion"); @@ -212,7 +243,7 @@ private static string CreateSourceEventFromManifest(ContractEventDescriptor ev, ev.Parameters[2].Type == ContractParameterType.Integer) { sourceCode.WriteLine($" [DisplayName(\"{ev.Name}\")]"); - sourceCode.WriteLine(" public event Neo.SmartContract.Testing.TestingStandards.INep17Standard.delTransfer? OnTransfer;"); + sourceCode.WriteLine($" public event {typeof(INep17Standard).FullName}.delTransfer? OnTransfer;"); return builder.ToString(); } @@ -225,7 +256,7 @@ private static string CreateSourceEventFromManifest(ContractEventDescriptor ev, ev.Parameters[1].Type == ContractParameterType.Hash160) { sourceCode.WriteLine($" [DisplayName(\"{ev.Name}\")]"); - sourceCode.WriteLine(" public event Neo.SmartContract.Testing.TestingStandards.IOwnable.delSetOwner? OnSetOwner;"); + sourceCode.WriteLine($" public event {typeof(IOwnable).FullName}.delSetOwner? OnSetOwner;"); return builder.ToString(); } diff --git a/src/Neo.SmartContract.Testing/Extensions/MockExtensions.cs b/src/Neo.SmartContract.Testing/Extensions/MockExtensions.cs index 857456612..300c42139 100644 --- a/src/Neo.SmartContract.Testing/Extensions/MockExtensions.cs +++ b/src/Neo.SmartContract.Testing/Extensions/MockExtensions.cs @@ -20,11 +20,40 @@ public static bool IsMocked(this Mock mock, MethodInfo method) foreach (var setup in mock.Setups) { if (setup.GetType() != methodCallType) continue; + if (property.GetValue(setup) is not MethodInfo mSetup) continue; - if (method.Equals(property.GetValue(setup))) + if (method.Equals(mSetup)) { return true; } + + // Sometimes method comparation with Equals doesn't work as expected with moq + + if (method.DeclaringType.Equals(mSetup.DeclaringType) && + method.Attributes.Equals(mSetup.Attributes) && + method.Name.Equals(mSetup.Name) && + method.ReturnType.Equals(mSetup.ReturnType) && + method.MetadataToken.Equals(mSetup.MetadataToken)) + { + var par1 = method.GetParameters(); + var par2 = mSetup.GetParameters(); + + if (par1.Length == par2.Length) + { + var eq = true; + + for (int i = 0; i < par1.Length; i++) + { + if (par1[i].ParameterType != par2[i].ParameterType) + { + eq = false; + break; + } + } + + if (eq) return true; + } + } } return false; diff --git a/src/Neo.SmartContract.Testing/Native/ContractManagement.cs b/src/Neo.SmartContract.Testing/Native/ContractManagement.cs index ddb60c955..96c1d941c 100644 --- a/src/Neo.SmartContract.Testing/Native/ContractManagement.cs +++ b/src/Neo.SmartContract.Testing/Native/ContractManagement.cs @@ -42,13 +42,13 @@ public abstract class ContractManagement : SmartContract /// Safe method /// [DisplayName("getContract")] - public abstract ContractState GetContract(UInt160 hash); + public abstract ContractState? GetContract(UInt160 hash); /// /// Safe method /// [DisplayName("getContractById")] - public abstract ContractState GetContractById(BigInteger id); + public abstract ContractState? GetContractById(BigInteger id); /// /// Safe method diff --git a/src/Neo.SmartContract.Testing/Native/OracleContract.cs b/src/Neo.SmartContract.Testing/Native/OracleContract.cs index bb3bc2b67..8b6d551ae 100644 --- a/src/Neo.SmartContract.Testing/Native/OracleContract.cs +++ b/src/Neo.SmartContract.Testing/Native/OracleContract.cs @@ -7,11 +7,11 @@ public abstract class OracleContract : SmartContract { #region Events - public delegate void delOracleRequest(BigInteger Id, UInt160 RequestContract, string Url, string Filter); + public delegate void delOracleRequest(ulong Id, UInt160 RequestContract, string Url, string Filter); [DisplayName("OracleRequest")] public event delOracleRequest? OnOracleRequest; - public delegate void delOracleResponse(BigInteger Id, UInt256 OriginalTx); + public delegate void delOracleResponse(ulong Id, UInt256 OriginalTx); [DisplayName("OracleResponse")] public event delOracleResponse? OnOracleResponse; diff --git a/src/Neo.SmartContract.Testing/Native/RoleManagement.cs b/src/Neo.SmartContract.Testing/Native/RoleManagement.cs index c01b1f491..e07ec9644 100644 --- a/src/Neo.SmartContract.Testing/Native/RoleManagement.cs +++ b/src/Neo.SmartContract.Testing/Native/RoleManagement.cs @@ -21,7 +21,7 @@ public abstract class RoleManagement : SmartContract /// Safe method /// [DisplayName("getDesignatedByRole")] - public abstract ECPoint[] GetDesignatedByRole(BigInteger? role, BigInteger? index); + public abstract ECPoint[] GetDesignatedByRole(Role? role, BigInteger? index); #endregion diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index aecfff398..8ed79e36a 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -376,7 +376,8 @@ public T FromHash(UInt160 hash, Action>? customMock = null, bool chec return MockContract(hash, null, customMock); } - var state = Native.ContractManagement.GetContract(hash); + var state = Native.ContractManagement.GetContract(hash) + ?? throw new Exception($"The contract {hash} does not exist."); return MockContract(state.Hash, state.Id, customMock); } diff --git a/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs b/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs index 1571b331c..9dbdab9d9 100644 --- a/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs +++ b/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs @@ -2,6 +2,7 @@ using Moq; using Neo.IO; using Neo.SmartContract.Manifest; +using Neo.SmartContract.Testing.Coverage; using Neo.SmartContract.Testing.InvalidTypes; using Neo.VM; using Neo.VM.Types; @@ -43,9 +44,25 @@ protected onNEP17PaymentContract(SmartContractInitialize initialize) : base(init #endregion /// - /// Initialize Test + /// Constructor /// - public Nep17Tests(string nefFile, string manifestFile) : base(nefFile, manifestFile) + /// Nef file + /// Manifest file + /// Debug info file + public Nep17Tests(string nefFile, string manifestFile, string? debugInfoFile = null) : + base(nefFile, manifestFile, debugInfoFile) + { + Contract.OnTransfer += onTransfer; + } + + /// + /// Constructor + /// + /// Nef file + /// Manifest + /// Debug info + public Nep17Tests(NefFile nefFile, ContractManifest manifestFile, NeoDebugInfo? debugInfo = null) + : base(nefFile, manifestFile, debugInfo) { Contract.OnTransfer += onTransfer; } @@ -191,7 +208,7 @@ public virtual void TestTransfer() // We create a mock contract using the current nef and manifest // Only we need to create the manifest method, then it will be redirected - ContractManifest manifest = ContractManifest.Parse(Manifest); + ContractManifest manifest = ContractManifest.Parse(Manifest.ToJson().ToString()); manifest.Abi.Methods = new ContractMethodDescriptor[] { new () @@ -214,7 +231,7 @@ public virtual void TestTransfer() BigInteger? calledAmount = null; UInt160? calledData = null; - var mock = Engine.Deploy(NefFile, manifest.ToJson().ToString(), null, m => + var mock = Engine.Deploy(NefFile, manifest, null, m => { m .Setup(s => s.onNEP17Payment(It.IsAny(), It.IsAny(), It.IsAny())) diff --git a/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs b/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs index 7a264c42e..2ffeb81a6 100644 --- a/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs +++ b/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs @@ -1,8 +1,12 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Testing.Coverage; using Neo.SmartContract.Testing.InvalidTypes; using Neo.VM; using System; using System.Collections.Generic; +using System.IO; namespace Neo.SmartContract.Testing.TestingStandards; @@ -16,9 +20,27 @@ public class OwnableTests : TestBase #endregion /// - /// Initialize Test + /// Constructor /// - public OwnableTests(string nefFile, string manifestFile) : base(nefFile, manifestFile) + /// Nef file + /// Manifest file + /// Debug info file + public OwnableTests(string nefFile, string manifestFile, string? debugInfoFile = null) + : base(File.ReadAllBytes(nefFile).AsSerializable(), + ContractManifest.Parse(File.ReadAllText(manifestFile)), + !string.IsNullOrEmpty(debugInfoFile) && NeoDebugInfo.TryLoad(debugInfoFile, out var debugInfo) ? debugInfo : null) + { + Contract.OnSetOwner += onSetOwner; + } + + /// + /// Constructor + /// + /// Nef file + /// Manifest + /// Debug info + public OwnableTests(NefFile nefFile, ContractManifest manifestFile, NeoDebugInfo? debugInfo = null) + : base(nefFile, manifestFile, debugInfo) { Contract.OnSetOwner += onSetOwner; } diff --git a/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs b/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs index 92c39fd0e..32fb8bceb 100644 --- a/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs +++ b/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Manifest; using Neo.SmartContract.Testing.Coverage; using System.IO; @@ -11,19 +13,36 @@ public class TestBase where T : SmartContract public static Signer Alice { get; } = TestEngine.GetNewSigner(); public static Signer Bob { get; } = TestEngine.GetNewSigner(); - public byte[] NefFile { get; } - public string Manifest { get; } + public NefFile NefFile { get; } + public ContractManifest Manifest { get; } + public NeoDebugInfo? DebugInfo { get; } public TestEngine Engine { get; } public T Contract { get; } public UInt160 ContractHash => Contract.Hash; /// - /// Initialize Test + /// Constructor /// - public TestBase(string nefFile, string manifestFile) + /// Nef file + /// Manifest file + /// Debug info file + public TestBase(string nefFile, string manifestFile, string? debugInfoFile = null) : + this(File.ReadAllBytes(nefFile).AsSerializable(), + ContractManifest.Parse(File.ReadAllText(manifestFile)), + !string.IsNullOrEmpty(debugInfoFile) && NeoDebugInfo.TryLoad(debugInfoFile, out var debugInfo) ? debugInfo : null) + { } + + /// + /// Constructor + /// + /// Nef file + /// Manifest + /// Debug info + public TestBase(NefFile nefFile, ContractManifest manifestFile, NeoDebugInfo? debugInfo = null) { - NefFile = File.ReadAllBytes(nefFile); - Manifest = File.ReadAllText(manifestFile); + NefFile = nefFile; + Manifest = manifestFile; + DebugInfo = debugInfo; Engine = new TestEngine(true); Engine.SetTransactionSigners(Alice); diff --git a/tests/Neo.SmartContract.Template.UnitTests/Neo.SmartContract.Template.UnitTests.csproj b/tests/Neo.SmartContract.Template.UnitTests/Neo.SmartContract.Template.UnitTests.csproj index c5e0f6788..8852d19ea 100644 --- a/tests/Neo.SmartContract.Template.UnitTests/Neo.SmartContract.Template.UnitTests.csproj +++ b/tests/Neo.SmartContract.Template.UnitTests/Neo.SmartContract.Template.UnitTests.csproj @@ -11,27 +11,23 @@ - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - + + + - - - + + + + + + + + + diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/CoverageContractTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/CoverageContractTests.cs new file mode 100644 index 000000000..a947c8507 --- /dev/null +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/CoverageContractTests.cs @@ -0,0 +1,58 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Template.UnitTests.templates.neocontractnep17; +using Neo.SmartContract.Template.UnitTests.templates.neocontractowner; +using Neo.SmartContract.Testing; +using Neo.SmartContract.Testing.Coverage; +using Neo.SmartContract.Testing.Coverage.Formats; + +namespace Neo.SmartContract.Template.UnitTests.templates +{ + [TestClass] + public class CoverageContractTests + { + /// + /// Required coverage to be success + /// + public static decimal RequiredCoverage { get; set; } = 1M; + + [AssemblyCleanup] + public static void EnsureCoverage() + { + // Join here all of your coverage sources + + var coverageNep17 = Nep17ContractTests.Coverage; + coverageNep17?.Join(OwnerContractTests.Coverage); + var coverageOwnable = OwnableContractTests.Coverage; + var coverageOracle = OracleRequestTests.Coverage; + + // Dump coverage to console + + Assert.IsNotNull(coverageNep17, "NEP17 coverage can't be null"); + Assert.IsNotNull(coverageOwnable, "Ownable coverage can't be null"); + Assert.IsNotNull(coverageOracle, "Oracle coverage can't be null"); + + var coverage = new CoveredCollection(coverageNep17, coverageOwnable, coverageOracle); + + // Dump current coverage + + Console.WriteLine(coverage.Dump()); + File.WriteAllText("coverage.instruction.html", coverage.Dump(DumpFormat.Html)); + + // Write the cobertura format + + File.WriteAllText("coverage.cobertura.xml", new CoberturaFormat( + (coverageNep17, Nep17Contract.DebugInfo), + (coverageOwnable, Ownable.DebugInfo), + (coverageOracle, OracleRequest.DebugInfo) + ).Dump()); + + // Write the report to the specific path + + CoverageReporting.CreateReport("coverage.cobertura.xml", "./coverageReport/"); + + // Ensure that the coverage is more than X% at the end of the tests + + Assert.IsTrue(coverage.CoveredLinesPercentage >= RequiredCoverage, $"Coverage is less than {RequiredCoverage:P2}"); + } + } +} diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/CoverageContractTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/CoverageContractTests.cs deleted file mode 100644 index b3ab1541d..000000000 --- a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/CoverageContractTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.SmartContract.Testing.Coverage; -using Neo.SmartContract.Testing.Coverage.Formats; - -namespace Neo.SmartContract.Template.UnitTests.templates.neocontractnep17 -{ - [TestClass] - public class CoverageContractTests - { - /// - /// Required coverage to be success - /// - public static decimal RequiredCoverage { get; set; } = 1M; - - [AssemblyCleanup] - public static void EnsureCoverage() - { - // Join here all of your coverage sources - - var coverage = Nep17ContractTests.Coverage; - coverage?.Join(OwnerContractTests.Coverage); - - // Dump coverage to console - - Assert.IsNotNull(coverage, "Coverage can't be null"); - Console.WriteLine(coverage.Dump()); - - // Write basic instruction html coverage - - File.WriteAllText("coverage.instruction.html", coverage.Dump(DumpFormat.Html)); - - // Load our debug file - - if (NeoDebugInfo.TryLoad("templates/neocontractnep17/Artifacts/Nep17Contract.nefdbgnfo", out var dbg)) - { - // Write the cobertura format - - File.WriteAllText("coverage.cobertura.xml", coverage.Dump(new CoberturaFormat((coverage, dbg)))); - - // Write the report to the specific path - - CoverageReporting.CreateReport("coverage.cobertura.xml", "./coverageReport/"); - } - - // Ensure that the coverage is more than X% at the end of the tests - - Assert.IsTrue(coverage.CoveredLinesPercentage >= 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 index de1c4de00..cff5fbe1d 100644 --- a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; using Neo.SmartContract.Testing; using Neo.SmartContract.Testing.InvalidTypes; using Neo.SmartContract.Testing.TestingStandards; @@ -24,12 +25,7 @@ public class Nep17ContractTests : Nep17Tests /// /// Initialize Test /// - public Nep17ContractTests() : - base( - "templates/neocontractnep17/Artifacts/Nep17Contract.nef", - "templates/neocontractnep17/Artifacts/Nep17Contract.manifest.json" - ) - { } + public Nep17ContractTests() : base(Nep17Contract.Nef, Nep17Contract.Manifest) { } [TestMethod] public void TestMyMethod() @@ -140,13 +136,13 @@ public void TestUpdate() Engine.SetTransactionSigners(Bob); - Assert.ThrowsException(() => Contract.Update(NefFile, Manifest)); + Assert.ThrowsException(() => Contract.Update(NefFile.ToArray(), Manifest.ToJson().ToString())); Engine.SetTransactionSigners(Alice); // Test Update with the same script - Contract.Update(NefFile, Manifest); + Contract.Update(NefFile.ToArray(), Manifest.ToJson().ToString()); // Ensure that it works with the same script diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/OwnerContractTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/OwnerContractTests.cs index b42cc142b..e9c8b6b01 100644 --- a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/OwnerContractTests.cs +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/OwnerContractTests.cs @@ -14,12 +14,7 @@ public class OwnerContractTests : OwnableTests /// /// Initialize Test /// - public OwnerContractTests() : - base( - "templates/neocontractnep17/Artifacts/Nep17Contract.nef", - "templates/neocontractnep17/Artifacts/Nep17Contract.manifest.json" - ) - { } + public OwnerContractTests() : base(Nep17Contract.Nef, Nep17Contract.Manifest) { } [TestMethod] public override void TestSetGetOwner() diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractoracle/OracleRequestTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractoracle/OracleRequestTests.cs new file mode 100644 index 000000000..38b3fd537 --- /dev/null +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractoracle/OracleRequestTests.cs @@ -0,0 +1,90 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Testing; +using Neo.SmartContract.Testing.TestingStandards; +using Neo.VM; +using System.Text; + +namespace Neo.SmartContract.Template.UnitTests.templates.neocontractowner +{ + /// + /// You need to build the solution to resolve Ownable class. + /// + [TestClass] + public class OracleRequestTests : TestBase + { + /// + /// Initialize Test + /// + public OracleRequestTests() : base(OracleRequest.Nef, OracleRequest.Manifest) { } + + [TestMethod] + public void TestGetResponse() + { + Assert.IsNull(Contract.Response); + } + + [TestMethod] + public void TestDoRequestAndFinish() + { + // Check without being oracle + + Assert.ThrowsException(() => Contract.OnOracleResponse(null, null, null, null)); + + // Check empty + + Assert.IsNull(Contract.Response); + + // Create request + + string response = ""; + ulong? requestId = null; + JToken data = JToken.Parse(@" +{ + ""id"": ""6520ad3c12a5d3765988542a"", + ""record"": { + ""propertyName"": ""Hello World!"" + } +}")!; + + Engine.Native.Oracle.OnOracleRequest += (ulong id, UInt160 requestContract, string url, string filter) => + { + requestId = id; + response = data.JsonPath(filter).ToString(false); + }; + + Contract.DoRequest(); + Assert.IsNotNull(requestId); + + // Execute error + + Engine.Transaction.Attributes = new[]{ + new OracleResponse() + { + Code = OracleResponseCode.Error, + Id = requestId.Value, + Result = Encoding.UTF8.GetBytes(response), + } + }; + Assert.ThrowsException(Engine.Native.Oracle.Finish); + + // Execute finish + + Engine.Transaction.Attributes = new[]{ + new OracleResponse() + { + Code = OracleResponseCode.Success, + Id = requestId.Value, + Result = Encoding.UTF8.GetBytes(response), + } + }; + + Engine.Native.Oracle.Finish(); + + // Check result + + Assert.AreEqual("Hello World!", Contract.Response); + } + } +} diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractowner/OwnableContractTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractowner/OwnableContractTests.cs new file mode 100644 index 000000000..3a0d79bdc --- /dev/null +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractowner/OwnableContractTests.cs @@ -0,0 +1,119 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.SmartContract.Testing; +using Neo.SmartContract.Testing.InvalidTypes; +using Neo.SmartContract.Testing.TestingStandards; +using Neo.VM; + +namespace Neo.SmartContract.Template.UnitTests.templates.neocontractowner +{ + /// + /// You need to build the solution to resolve Ownable class. + /// + [TestClass] + public class OwnableContractTests : OwnableTests + { + /// + /// Initialize Test + /// + public OwnableContractTests() : base(Ownable.Nef, Ownable.Manifest) { } + + [TestMethod] + public override void TestSetGetOwner() + { + base.TestSetGetOwner(); + + // Test throw if was stored an invalid owner + // Technically not possible, but raise 100% coverage + + Contract.Storage.Put(new byte[] { 0xff }, 123); + Assert.ThrowsException(() => Contract.Owner); + } + + [TestMethod] + public void TestMyMethod() + { + Assert.AreEqual("World", Contract.MyMethod()); + } + + [TestMethod] + public void TestUpdate() + { + // Alice is the deployer + + Engine.SetTransactionSigners(Bob); + + Assert.ThrowsException(() => Contract.Update(NefFile.ToArray(), Manifest.ToJson().ToString())); + + Engine.SetTransactionSigners(Alice); + + // Test Update with the same script + + Contract.Update(NefFile.ToArray(), Manifest.ToJson().ToString()); + + // Ensure that it works with the same script + + TestVerify(); + } + + [TestMethod] + public void TestDeployWithOwner() + { + // Alice is the deployer + + Engine.SetTransactionSigners(Bob); + + // Try with invalid owners + + Assert.ThrowsException(() => Engine.Deploy(NefFile, Manifest, UInt160.Zero)); + Assert.ThrowsException(() => Engine.Deploy(NefFile, Manifest, InvalidUInt160.InvalidLength)); + Assert.ThrowsException(() => Engine.Deploy(NefFile, Manifest, InvalidUInt160.InvalidType)); + + // 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); + } + + [TestMethod] + public void TestDestroy() + { + // Try without being owner + + Engine.SetTransactionSigners(Bob); + Assert.ThrowsException(Contract.Destroy); + + // Try with the owner + + var checkpoint = Engine.Checkpoint(); + + Engine.SetTransactionSigners(Alice); + Contract.Destroy(); + Assert.IsNull(Engine.Native.ContractManagement.GetContract(Contract)); + + Engine.Restore(checkpoint); + } + } +} diff --git a/tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs index 1039ea855..53731b31b 100644 --- a/tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs +++ b/tests/Neo.SmartContract.Testing.UnitTests/Coverage/CoverageDataTests.cs @@ -18,7 +18,7 @@ public void TestDump() Assert.AreEqual(100_000_000, engine.Native.NEO.TotalSupply); Assert.AreEqual(@" -0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5 [5.26% - 100.00%] +NeoToken [0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5] [5.26% - 100.00%] ┌-───────────────────────────────-┬-───────-┬-───────-┐ │ Method │ Line │ Branch │ ├-───────────────────────────────-┼-───────-┼-───────-┤ @@ -45,7 +45,7 @@ 0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5 [5.26% - 100.00%] ".Trim(), engine.GetCoverage(engine.Native.NEO)?.Dump().Trim()); Assert.AreEqual(@" -0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5 [5.26% - 100.00%] +NeoToken [0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5] [5.26% - 100.00%] ┌-─────────────-┬-───────-┬-───────-┐ │ Method │ Line │ Branch │ ├-─────────────-┼-───────-┼-───────-┤ diff --git a/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs index f8186d8b3..13acd8cbd 100644 --- a/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs +++ b/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs @@ -28,6 +28,12 @@ namespace Neo.SmartContract.Testing; public abstract class Contract1 : Neo.SmartContract.Testing.SmartContract, Neo.SmartContract.Testing.TestingStandards.INep17Standard, Neo.SmartContract.Testing.TestingStandards.IVerificable { + #region Compiled data + + public static readonly Neo.SmartContract.Manifest.ContractManifest Manifest = Neo.SmartContract.Manifest.ContractManifest.Parse(@""{""""name"""":""""Contract1"""",""""groups"""":[],""""features"""":{},""""supportedstandards"""":[""""NEP-17""""],""""abi"""":{""""methods"""":[{""""name"""":""""symbol"""",""""parameters"""":[],""""returntype"""":""""String"""",""""offset"""":1406,""""safe"""":true},{""""name"""":""""decimals"""",""""parameters"""":[],""""returntype"""":""""Integer"""",""""offset"""":1421,""""safe"""":true},{""""name"""":""""totalSupply"""",""""parameters"""":[],""""returntype"""":""""Integer"""",""""offset"""":43,""""safe"""":true},{""""name"""":""""balanceOf"""",""""parameters"""":[{""""name"""":""""owner"""",""""type"""":""""Hash160""""}],""""returntype"""":""""Integer"""",""""offset"""":85,""""safe"""":true},{""""name"""":""""transfer"""",""""parameters"""":[{""""name"""":""""from"""",""""type"""":""""Hash160""""},{""""name"""":""""to"""",""""type"""":""""Hash160""""},{""""name"""":""""amount"""",""""type"""":""""Integer""""},{""""name"""":""""data"""",""""type"""":""""Any""""}],""""returntype"""":""""Boolean"""",""""offset"""":281,""""safe"""":false},{""""name"""":""""getOwner"""",""""parameters"""":[],""""returntype"""":""""Hash160"""",""""offset"""":711,""""safe"""":true},{""""name"""":""""setOwner"""",""""parameters"""":[{""""name"""":""""newOwner"""",""""type"""":""""Hash160""""}],""""returntype"""":""""Void"""",""""offset"""":755,""""safe"""":false},{""""name"""":""""burn"""",""""parameters"""":[{""""name"""":""""account"""",""""type"""":""""Hash160""""},{""""name"""":""""amount"""",""""type"""":""""Integer""""}],""""returntype"""":""""Void"""",""""offset"""":873,""""safe"""":false},{""""name"""":""""mint"""",""""parameters"""":[{""""name"""":""""to"""",""""type"""":""""Hash160""""},{""""name"""":""""amount"""",""""type"""":""""Integer""""}],""""returntype"""":""""Void"""",""""offset"""":915,""""safe"""":false},{""""name"""":""""withdraw"""",""""parameters"""":[{""""name"""":""""token"""",""""type"""":""""Hash160""""},{""""name"""":""""to"""",""""type"""":""""Hash160""""},{""""name"""":""""amount"""",""""type"""":""""Integer""""}],""""returntype"""":""""Boolean"""",""""offset"""":957,""""safe"""":false},{""""name"""":""""onNEP17Payment"""",""""parameters"""":[{""""name"""":""""from"""",""""type"""":""""Hash160""""},{""""name"""":""""amount"""",""""type"""":""""Integer""""},{""""name"""":""""data"""",""""type"""":""""Any""""}],""""returntype"""":""""Void"""",""""offset"""":1139,""""safe"""":false},{""""name"""":""""verify"""",""""parameters"""":[],""""returntype"""":""""Boolean"""",""""offset"""":1203,""""safe"""":true},{""""name"""":""""myMethod"""",""""parameters"""":[],""""returntype"""":""""String"""",""""offset"""":1209,""""safe"""":false},{""""name"""":""""_deploy"""",""""parameters"""":[{""""name"""":""""data"""",""""type"""":""""Any""""},{""""name"""":""""update"""",""""type"""":""""Boolean""""}],""""returntype"""":""""Void"""",""""offset"""":1229,""""safe"""":false},{""""name"""":""""update"""",""""parameters"""":[{""""name"""":""""nefFile"""",""""type"""":""""ByteArray""""},{""""name"""":""""manifest"""",""""type"""":""""String""""}],""""returntype"""":""""Void"""",""""offset"""":1352,""""safe"""":false},{""""name"""":""""_initialize"""",""""parameters"""":[],""""returntype"""":""""Void"""",""""offset"""":1390,""""safe"""":false}],""""events"""":[{""""name"""":""""Transfer"""",""""parameters"""":[{""""name"""":""""from"""",""""type"""":""""Hash160""""},{""""name"""":""""to"""",""""type"""":""""Hash160""""},{""""name"""":""""amount"""",""""type"""":""""Integer""""}]},{""""name"""":""""SetOwner"""",""""parameters"""":[{""""name"""":""""newOwner"""",""""type"""":""""Hash160""""}]}]},""""permissions"""":[{""""contract"""":""""*"""",""""methods"""":""""*""""}],""""trusts"""":[],""""extra"""":{""""Author"""":""""\u003CYour Name Or Company Here\u003E"""",""""Description"""":""""\u003CDescription Here\u003E"""",""""Email"""":""""\u003CYour Public Email Here\u003E"""",""""Version"""":""""\u003CVersion String Here\u003E""""}}""); + + #endregion + #region Events public delegate void delSetOwner(UInt160? newOwner);