From 0242d9620b545c7a9a0206f4aa7fe300074cee8c Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Thu, 29 Feb 2024 07:13:09 +0100 Subject: [PATCH 01/11] Some fixes --- src/Neo.Compiler.CSharp/CompilationEngine.cs | 2 +- src/Neo.Compiler.CSharp/CompilationOptions.cs | 2 +- .../Coverage/CoveredCollection.cs | 10 +++--- .../Coverage/CoveredContract.cs | 10 +++--- .../Coverage/CoveredMethod.cs | 10 +++--- .../Coverage/Formats/ConsoleFormat.cs | 10 +++--- .../Extensions/ArtifactExtensions.cs | 18 ++++++++-- .../Extensions/TestExtensions.cs | 2 +- src/Neo.SmartContract.Testing/README.md | 1 + .../SmartContract.cs | 28 ++++++++++----- src/Neo.SmartContract.Testing/TestEngine.cs | 5 +++ .../TestingStandards/TestBase.cs | 34 +++++++++++++++++++ .../Nep17ContractTemplate.artifacts.cs | 1 + .../OracleRequestTemplate.artifacts.cs | 1 + .../OwnableTemplate.artifacts.cs | 1 + .../Extensions/ArtifactExtensionsTests.cs | 1 + 16 files changed, 101 insertions(+), 35 deletions(-) diff --git a/src/Neo.Compiler.CSharp/CompilationEngine.cs b/src/Neo.Compiler.CSharp/CompilationEngine.cs index 71a4805a0..7f73e1792 100644 --- a/src/Neo.Compiler.CSharp/CompilationEngine.cs +++ b/src/Neo.Compiler.CSharp/CompilationEngine.cs @@ -249,7 +249,7 @@ public Compilation GetCompilation(string csproj) if (!remove.Contains("*.cs")) { var obj = Path.Combine(folder, "obj"); - var binSc = Path.Combine(Path.Combine(folder, "bin"), "sc"); + var binSc = Path.Combine(folder, "bin"); foreach (var entry in Directory.EnumerateFiles(folder, "*.cs", SearchOption.AllDirectories) .Where(p => !p.StartsWith(obj) && !p.StartsWith(binSc)) .Select(u => u)) diff --git a/src/Neo.Compiler.CSharp/CompilationOptions.cs b/src/Neo.Compiler.CSharp/CompilationOptions.cs index 388d8b6bb..77bd1106c 100644 --- a/src/Neo.Compiler.CSharp/CompilationOptions.cs +++ b/src/Neo.Compiler.CSharp/CompilationOptions.cs @@ -32,7 +32,7 @@ public enum OptimizationType : byte public OptimizationType Optimize { get; set; } = OptimizationType.Basic; public bool Checked { get; set; } public bool NoInline { get; set; } - public byte AddressVersion { get; set; } + public byte AddressVersion { get; set; } = 0x35; public string? BaseName { get; set; } private CSharpParseOptions? parseOptions = null; diff --git a/src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs b/src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs index 89ca49d13..5cb1bbf67 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoveredCollection.cs @@ -61,12 +61,12 @@ public CoveredCollection(params CoverageBase[] entries) /// Coverage dump public override string Dump(DumpFormat format = DumpFormat.Console) { - switch (format) + return format switch { - case DumpFormat.Console: return new ConsoleFormat(GetEntries()).Dump(); - case DumpFormat.Html: return new IntructionHtmlFormat(GetEntries()).Dump(); - default: throw new NotImplementedException(); - } + DumpFormat.Console => new ConsoleFormat(GetEntries()).Dump(), + DumpFormat.Html => new IntructionHtmlFormat(GetEntries()).Dump(), + _ => throw new NotImplementedException(), + }; } /// diff --git a/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs b/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs index 02bedc97a..dc10ce3d8 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoveredContract.cs @@ -292,12 +292,12 @@ public void Join(CoverageBase? coverage) /// Coverage dump public override string Dump(DumpFormat format = DumpFormat.Console) { - switch (format) + return format switch { - case DumpFormat.Console: return new ConsoleFormat(this).Dump(); - case DumpFormat.Html: return new IntructionHtmlFormat(this).Dump(); - default: throw new NotImplementedException(); - } + DumpFormat.Console => new ConsoleFormat(this).Dump(), + DumpFormat.Html => new IntructionHtmlFormat(this).Dump(), + _ => throw new NotImplementedException(), + }; } /// diff --git a/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs b/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs index 56d898b4b..a909fe637 100644 --- a/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs +++ b/src/Neo.SmartContract.Testing/Coverage/CoveredMethod.cs @@ -61,12 +61,12 @@ public CoveredMethod(CoveredContract contract, ContractMethodDescriptor method, /// Coverage dump public override string Dump(DumpFormat format = DumpFormat.Console) { - switch (format) + return format switch { - 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(); - } + DumpFormat.Console => new ConsoleFormat(Contract, m => ReferenceEquals(m, this)).Dump(), + DumpFormat.Html => new IntructionHtmlFormat(Contract, m => ReferenceEquals(m, this)).Dump(), + _ => throw new NotImplementedException(), + }; } public override string ToString() => Method.ToString(); diff --git a/src/Neo.SmartContract.Testing/Coverage/Formats/ConsoleFormat.cs b/src/Neo.SmartContract.Testing/Coverage/Formats/ConsoleFormat.cs index 0f63e5189..d5c72519c 100644 --- a/src/Neo.SmartContract.Testing/Coverage/Formats/ConsoleFormat.cs +++ b/src/Neo.SmartContract.Testing/Coverage/Formats/ConsoleFormat.cs @@ -19,7 +19,7 @@ public partial class ConsoleFormat : CoverageFormatBase /// Method Filter public ConsoleFormat(CoveredContract contract, Func? filter = null) { - Entries = new (CoveredContract, Func?)[] { (contract, filter) }; + Entries = new[] { (contract, filter) }; } /// @@ -69,13 +69,13 @@ private void WriteReport(StreamWriter writer) 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[2], '─')}-┐"); + 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[2])} │"); + writer.WriteLine($"├-{"─".PadLeft(max[0], '─')}-┼-{"─".PadLeft(max[1], '─')}-┼-{"─".PadLeft(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($"│ {string.Format($"{{0,-{max[0]}}}", print[0], max[0])} │ {string.Format($"{{0,{max[1]}}}", print[1], max[1])} │ {string.Format($"{{0,{max[2]}}}", print[2], max[2])} │"); } writer.WriteLine($"└-{"─".PadLeft(max[0], '─')}-┴-{"─".PadLeft(max[1], '─')}-┴-{"─".PadLeft(max[2], '─')}-┘"); diff --git a/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs b/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs index 3c86e9a05..eb7b19708 100644 --- a/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs +++ b/src/Neo.SmartContract.Testing/Extensions/ArtifactExtensions.cs @@ -1,7 +1,5 @@ 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; @@ -58,6 +56,7 @@ public static string GetArtifactsSource(this ContractManifest manifest, string? if (manifest.IsVerificable()) inheritance.Add(typeof(IVerificable)); sourceCode.WriteLine("using Neo.Cryptography.ECC;"); + sourceCode.WriteLine("using System;"); sourceCode.WriteLine("using System.Collections.Generic;"); sourceCode.WriteLine("using System.ComponentModel;"); sourceCode.WriteLine("using System.Numerics;"); @@ -180,6 +179,7 @@ public static string GetArtifactsSource(this ContractManifest manifest, string? private static (ContractMethodDescriptor[] methods, (ContractMethodDescriptor getter, ContractMethodDescriptor? setter)[] properties) ProcessAbiMethods(ContractMethodDescriptor[] methods) { + HashSet propertyNames = new(); List methodList = new(methods); List<(ContractMethodDescriptor, ContractMethodDescriptor?)> properties = new(); @@ -199,6 +199,13 @@ private static (ContractMethodDescriptor[] methods, (ContractMethodDescriptor ge u.ReturnType == ContractParameterType.Void ) : null; + // Avoid property repetition + + var propertyName = GetPropertyName(getter); + if (!propertyNames.Add(propertyName)) continue; + + // Add property and remove method + properties.Add((getter, setter)); methodList.Remove(getter); @@ -211,6 +218,11 @@ private static (ContractMethodDescriptor[] methods, (ContractMethodDescriptor ge return (methodList.ToArray(), properties.ToArray()); } + private static string GetPropertyName(ContractMethodDescriptor getter) + { + return TongleLowercase(EscapeName(getter.Name.StartsWith("get") ? getter.Name[3..] : getter.Name)); + } + /// /// Create source code from event /// @@ -296,7 +308,7 @@ private static string CreateSourceEventFromManifest(ContractEventDescriptor ev, /// Source private static string CreateSourcePropertyFromManifest(ContractMethodDescriptor getter, ContractMethodDescriptor? setter) { - var propertyName = TongleLowercase(EscapeName(getter.Name.StartsWith("get") ? getter.Name[3..] : getter.Name)); + var propertyName = GetPropertyName(getter); var getset = setter is not null ? $"{{ [DisplayName(\"{getter.Name}\")] get; [DisplayName(\"{setter.Name}\")] set; }}" : $"{{ [DisplayName(\"{getter.Name}\")] get; }}"; var builder = new StringBuilder(); diff --git a/src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs b/src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs index b09127c5c..a95043eee 100644 --- a/src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs +++ b/src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs @@ -1,4 +1,3 @@ -using Akka.Util; using Neo.Cryptography.ECC; using Neo.SmartContract.Testing.Attributes; using Neo.VM.Types; @@ -50,6 +49,7 @@ public static class TestExtensions return type switch { + _ when type == stackItem.GetType() => stackItem, _ when type == typeof(object) => stackItem, _ when type == typeof(string) => Utility.StrictUTF8.GetString(stackItem.GetSpan()), _ when type == typeof(byte[]) => stackItem.GetSpan().ToArray(), diff --git a/src/Neo.SmartContract.Testing/README.md b/src/Neo.SmartContract.Testing/README.md index 5c0e89ac6..d329c549a 100644 --- a/src/Neo.SmartContract.Testing/README.md +++ b/src/Neo.SmartContract.Testing/README.md @@ -84,6 +84,7 @@ And for read and write, we have: - **Gas**: Sets the gas execution limit for contract calls. Sets the `NetworkFee` of the `Transaction` object. - **EnableCoverageCapture**: Enables or disables the coverage capture. - **Trigger**: The trigger of the execution. +- **CallFlags**: Define the `CallFlags` for the mocked function, `All` by default. - **OnGetEntryScriptHash**: This feature makes it easy to change the EntryScriptHash. - **OnGetCallingScriptHash**: This feature makes it easy to change the CallingScriptHash. diff --git a/src/Neo.SmartContract.Testing/SmartContract.cs b/src/Neo.SmartContract.Testing/SmartContract.cs index 38360cbbf..f95ece1ef 100644 --- a/src/Neo.SmartContract.Testing/SmartContract.cs +++ b/src/Neo.SmartContract.Testing/SmartContract.cs @@ -55,6 +55,20 @@ internal StackItem Invoke(string methodName, params object[] args) using ScriptBuilder script = new(); + ConvertArgs(script, args); + + script.EmitPush(Engine.CallFlags); + script.EmitPush(methodName); + script.EmitPush(Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); + + // Execute + + return Engine.Execute(script.ToArray()); + } + + private void ConvertArgs(ScriptBuilder script, object[] args) + { if (args is null || args.Length == 0) script.Emit(OpCode.NEWARRAY0); else @@ -62,6 +76,11 @@ internal StackItem Invoke(string methodName, params object[] args) for (int i = args.Length - 1; i >= 0; i--) { var arg = args[i]; + if (arg is object[] arg2) + { + ConvertArgs(script, arg2); + break; + } if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.InvalidLength) || ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidLength)) @@ -79,15 +98,6 @@ internal StackItem Invoke(string methodName, params object[] args) 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 - - return Engine.Execute(script.ToArray()); } /// diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index 8ed79e36a..1b185f148 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -172,6 +172,11 @@ public long Gas /// public UInt160 Sender => Transaction.Sender; + /// + /// Call flags + /// + public CallFlags CallFlags { get; set; } = CallFlags.All; + /// /// Native artifacts /// diff --git a/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs b/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs index 32fb8bceb..98cb07531 100644 --- a/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs +++ b/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs @@ -3,12 +3,15 @@ using Neo.Network.P2P.Payloads; using Neo.SmartContract.Manifest; using Neo.SmartContract.Testing.Coverage; +using System.Collections.Generic; using System.IO; namespace Neo.SmartContract.Testing.TestingStandards; public class TestBase where T : SmartContract { + private readonly List _contractLogs = new(); + public static CoveredContract? Coverage { get; private set; } public static Signer Alice { get; } = TestEngine.GetNewSigner(); public static Signer Bob { get; } = TestEngine.GetNewSigner(); @@ -53,13 +56,44 @@ public TestBase(NefFile nefFile, ContractManifest manifestFile, NeoDebugInfo? de Coverage = Contract.GetCoverage()!; Assert.IsNotNull(Coverage); } + + Contract.OnRuntimeLog += Contract_OnRuntimeLog; + } + + private void Contract_OnRuntimeLog(string message) + { + _contractLogs.Add(message); } + #region Asserts + + /// + /// Assert that Log event was raised + /// + /// Logs + public void AssertLogs(params string[] logs) + { + Assert.AreEqual(logs.Length, _contractLogs.Count); + CollectionAssert.AreEqual(_contractLogs, logs); + _contractLogs.Clear(); + } + + /// + /// Assert that Transfer event was NOT raised + /// + public void AssertNoLogs() + { + Assert.AreEqual(0, _contractLogs.Count); + } + + #endregion + [TestCleanup] public virtual void OnCleanup() { // Join the current coverage into the static one + Contract.OnRuntimeLog -= Contract_OnRuntimeLog; Coverage?.Join(Contract.GetCoverage()); } } diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/TestingArtifacts/Nep17ContractTemplate.artifacts.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/TestingArtifacts/Nep17ContractTemplate.artifacts.cs index 568dab020..04eb870be 100644 --- a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/TestingArtifacts/Nep17ContractTemplate.artifacts.cs +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/TestingArtifacts/Nep17ContractTemplate.artifacts.cs @@ -1,4 +1,5 @@ using Neo.Cryptography.ECC; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Numerics; diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractoracle/TestingArtifacts/OracleRequestTemplate.artifacts.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractoracle/TestingArtifacts/OracleRequestTemplate.artifacts.cs index fbcd64e27..f74674adb 100644 --- a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractoracle/TestingArtifacts/OracleRequestTemplate.artifacts.cs +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractoracle/TestingArtifacts/OracleRequestTemplate.artifacts.cs @@ -1,4 +1,5 @@ using Neo.Cryptography.ECC; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Numerics; diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractowner/TestingArtifacts/OwnableTemplate.artifacts.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractowner/TestingArtifacts/OwnableTemplate.artifacts.cs index a6b8a1ad8..d12830297 100644 --- a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractowner/TestingArtifacts/OwnableTemplate.artifacts.cs +++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractowner/TestingArtifacts/OwnableTemplate.artifacts.cs @@ -1,4 +1,5 @@ using Neo.Cryptography.ECC; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Numerics; diff --git a/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs index 13acd8cbd..4dfd75ef5 100644 --- a/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs +++ b/tests/Neo.SmartContract.Testing.UnitTests/Extensions/ArtifactExtensionsTests.cs @@ -20,6 +20,7 @@ public void TestGetArtifactsSource() Assert.AreEqual(source, @" using Neo.Cryptography.ECC; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Numerics; From 54d8785c5bcbabe04333acea354ac3b417504532 Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 28 Feb 2024 22:30:28 -0800 Subject: [PATCH 02/11] Update TestEngine.cs --- src/Neo.SmartContract.Testing/TestEngine.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index 1b185f148..87864a1a8 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -676,6 +676,14 @@ public void SetTransactionSigners(params UInt160[] signers) Transaction.Signers = signers.Select(u => new Signer() { Account = u, Scopes = WitnessScope.CalledByEntry }).ToArray(); } + /// + /// Clear Transaction Signers + /// + public void SetTransactionSigners() + { + Transaction.Signers = System.Array.Empty(); + } + /// /// Set Transaction Signers /// From 56aa8ed8742a75cf9003561265a573d2c2021289 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Feb 2024 00:08:48 -0800 Subject: [PATCH 03/11] Update TestBase.cs --- src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs b/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs index 98cb07531..7d2fa79d2 100644 --- a/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs +++ b/src/Neo.SmartContract.Testing/TestingStandards/TestBase.cs @@ -13,8 +13,8 @@ public class TestBase where T : SmartContract private readonly List _contractLogs = new(); public static CoveredContract? Coverage { get; private set; } - public static Signer Alice { get; } = TestEngine.GetNewSigner(); - public static Signer Bob { get; } = TestEngine.GetNewSigner(); + public static Signer Alice { get; set; } = TestEngine.GetNewSigner(); + public static Signer Bob { get; set; } = TestEngine.GetNewSigner(); public NefFile NefFile { get; } public ContractManifest Manifest { get; } From c1d064ca4c02b254c7d4a2ddcc2af18621d2cae2 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Feb 2024 00:11:53 -0800 Subject: [PATCH 04/11] Update TestEngine.cs --- src/Neo.SmartContract.Testing/TestEngine.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index 87864a1a8..cf5faf9ff 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -211,9 +211,13 @@ public TestEngine(ProtocolSettings settings, bool initializeNativeContracts = tr Transaction = new Transaction() { + Version = 0, Attributes = System.Array.Empty(), Script = System.Array.Empty(), NetworkFee = ApplicationEngine.TestModeGas, + SystemFee = 0, + ValidUntilBlock = 0, + Nonce = 0x01020304, Signers = new Signer[] { new() @@ -658,6 +662,14 @@ public StackItem Execute(Script script) }; } + /// + /// Clear Transaction Signers + /// + public void SetTransactionSigners() + { + Transaction.Signers = System.Array.Empty(); + } + /// /// Set Transaction signers /// @@ -676,14 +688,6 @@ public void SetTransactionSigners(params UInt160[] signers) Transaction.Signers = signers.Select(u => new Signer() { Account = u, Scopes = WitnessScope.CalledByEntry }).ToArray(); } - /// - /// Clear Transaction Signers - /// - public void SetTransactionSigners() - { - Transaction.Signers = System.Array.Empty(); - } - /// /// Set Transaction Signers /// From 1bc80df12c9629e71c20f6070791dc631c58da5b Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Feb 2024 00:26:27 -0800 Subject: [PATCH 05/11] Add id to SmartContract storage --- .../Storage/SmartContractStorage.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Neo.SmartContract.Testing/Storage/SmartContractStorage.cs b/src/Neo.SmartContract.Testing/Storage/SmartContractStorage.cs index a521471a6..aea346f36 100644 --- a/src/Neo.SmartContract.Testing/Storage/SmartContractStorage.cs +++ b/src/Neo.SmartContract.Testing/Storage/SmartContractStorage.cs @@ -10,6 +10,11 @@ public class SmartContractStorage private readonly SmartContract _smartContract; private int? _contractId; + /// + /// Storage Id + /// + public int Id => GetContractId(); + /// /// Constructor /// From b204b80c891277c4862c149ae881dedbf8d9b911 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Feb 2024 00:44:30 -0800 Subject: [PATCH 06/11] Update TestEngine.cs --- src/Neo.SmartContract.Testing/TestEngine.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index cf5faf9ff..ea59ba860 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -679,6 +679,20 @@ public void SetTransactionSigners(params Signer[] signers) Transaction.Signers = signers; } + /// + /// Set Transaction Signers using CalledByEntry + /// + /// Signers + public void SetTransactionSigners(params ECPoint[] signers) + { + Transaction.Signers = signers.Select(pubkey => new Signer() + { + Account = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash(), + Scopes = WitnessScope.CalledByEntry + }) + .ToArray(); + } + /// /// Set Transaction Signers using CalledByEntry /// From e5a729dd785e18242c8bece96ab23cddf2c8e066 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Feb 2024 00:46:56 -0800 Subject: [PATCH 07/11] Update TestEngine.cs --- src/Neo.SmartContract.Testing/TestEngine.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index ea59ba860..0a9e62cb3 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -684,11 +684,21 @@ public void SetTransactionSigners(params Signer[] signers) /// /// Signers public void SetTransactionSigners(params ECPoint[] signers) + { + SetTransactionSigners(WitnessScope.CalledByEntry, signers); + } + + /// + /// Set Transaction Signers + /// + /// Scope + /// Signers + public void SetTransactionSigners(WitnessScope scope, params ECPoint[] signers) { Transaction.Signers = signers.Select(pubkey => new Signer() { Account = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash(), - Scopes = WitnessScope.CalledByEntry + Scopes = scope }) .ToArray(); } From 9bf46b838a011ef7377a4df1aa63aca4ac5d08f2 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Feb 2024 01:16:58 -0800 Subject: [PATCH 08/11] Allow to use pointers --- src/Neo.SmartContract.Testing/TestEngine.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index 0a9e62cb3..09aa8ac9d 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -530,8 +530,10 @@ public bool ReleaseMock(SmartContract contract) /// Execute raw script /// /// Script + /// Initial position (default=0) + /// Before execute /// StackItem - public StackItem Execute(Script script) + public StackItem Execute(Script script, int initialPosition = 0, Action? beforeExecute = null) { // Store the script in current transaction @@ -543,7 +545,7 @@ public StackItem Execute(Script script) using var engine = new TestingApplicationEngine(this, Trigger, Transaction, snapshot, CurrentBlock); - engine.LoadScript(script); + engine.LoadScript(script, initialPosition: initialPosition); // Clean events, if we Execute inside and execute // becaus it's a mock, we can register twice @@ -558,6 +560,7 @@ public StackItem Execute(Script script) // Execute + beforeExecute?.Invoke(engine); var executionResult = engine.Execute(); // Detach to static event From d18e6298850a299dfdcdf78bb289ef841629d7c0 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Thu, 29 Feb 2024 12:46:50 +0100 Subject: [PATCH 09/11] Update --- .../DynamicArgumentSyscall.cs | 45 ++++++++++++++++++ .../InvalidTypes/InvalidECPoint.cs | 22 +++++++++ .../SmartContract.cs | 47 ++++++++++++++++--- 3 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 src/Neo.SmartContract.Testing/DynamicArgumentSyscall.cs create mode 100644 src/Neo.SmartContract.Testing/InvalidTypes/InvalidECPoint.cs diff --git a/src/Neo.SmartContract.Testing/DynamicArgumentSyscall.cs b/src/Neo.SmartContract.Testing/DynamicArgumentSyscall.cs new file mode 100644 index 000000000..4351f1e05 --- /dev/null +++ b/src/Neo.SmartContract.Testing/DynamicArgumentSyscall.cs @@ -0,0 +1,45 @@ +using Neo.Cryptography; +using Neo.VM.Types; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; + +namespace Neo.SmartContract.Testing +{ + internal class DynamicArgumentSyscall + { + private readonly List> _actions = new(); + + /// + /// Syscall Name + /// + public const string Name = $"{nameof(DynamicArgumentSyscall)}.Invoke"; + + /// + /// Syscall hash + /// + public static uint Hash => BinaryPrimitives.ReadUInt32LittleEndian(Encoding.ASCII.GetBytes(Name).Sha256()); + + /// + /// Add action + /// + /// Action + /// Sycall argument + public int Add(Func action) + { + _actions.Add(action); + return _actions.Count - 1; + } + + /// + /// Get item + /// + /// Index + /// Stack item + public StackItem Invoke(int index) + { + return _actions[index](); + } + } +} diff --git a/src/Neo.SmartContract.Testing/InvalidTypes/InvalidECPoint.cs b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidECPoint.cs new file mode 100644 index 000000000..907edd9dd --- /dev/null +++ b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidECPoint.cs @@ -0,0 +1,22 @@ +using Neo.Cryptography.ECC; + +namespace Neo.SmartContract.Testing.InvalidTypes +{ + public class InvalidECPoint + { + /// + /// Null ECPoint + /// + public static readonly ECPoint? Null = null; + + /// + /// This will be an invalid ECPoint (ByteString) + /// + public static readonly ECPoint InvalidLength = new(); + + /// + /// This will be an invalid ECPoint (Integer) + /// + public static readonly ECPoint InvalidType = new(); + } +} diff --git a/src/Neo.SmartContract.Testing/SmartContract.cs b/src/Neo.SmartContract.Testing/SmartContract.cs index f95ece1ef..3a960e0c8 100644 --- a/src/Neo.SmartContract.Testing/SmartContract.cs +++ b/src/Neo.SmartContract.Testing/SmartContract.cs @@ -53,9 +53,10 @@ internal StackItem Invoke(string methodName, params object[] args) { // Compose script + DynamicArgumentSyscall? dynArgument = null; using ScriptBuilder script = new(); - ConvertArgs(script, args); + ConvertArgs(script, args, ref dynArgument); script.EmitPush(Engine.CallFlags); script.EmitPush(methodName); @@ -64,10 +65,17 @@ internal StackItem Invoke(string methodName, params object[] args) // Execute - return Engine.Execute(script.ToArray()); + return Engine.Execute(script.ToArray(), 0, dynArgument is null ? null : engine => ConfigureEngine(engine, dynArgument)); } - private void ConvertArgs(ScriptBuilder script, object[] args) + private void ConfigureEngine(ApplicationEngine engine, DynamicArgumentSyscall dynArgument) + { + if (engine is not TestingApplicationEngine testEngine) throw new InvalidOperationException(); + + testEngine.DynamicArgumentSyscall = dynArgument; + } + + private void ConvertArgs(ScriptBuilder script, object[] args, ref DynamicArgumentSyscall? dynArgument) { if (args is null || args.Length == 0) script.Emit(OpCode.NEWARRAY0); @@ -76,22 +84,47 @@ private void ConvertArgs(ScriptBuilder script, object[] args) for (int i = args.Length - 1; i >= 0; i--) { var arg = args[i]; + if (arg is object[] arg2) { - ConvertArgs(script, arg2); - break; + ConvertArgs(script, arg2, ref dynArgument); + continue; + } + else if (arg is IEnumerable argEnumerable) + { + ConvertArgs(script, argEnumerable.ToArray(), ref dynArgument); + continue; } if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.InvalidLength) || - ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidLength)) + ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidLength) || + ReferenceEquals(arg, InvalidTypes.InvalidECPoint.InvalidLength)) { arg = System.Array.Empty(); } else if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.InvalidType) || - ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidType)) + ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidType) || + ReferenceEquals(arg, InvalidTypes.InvalidECPoint.InvalidType)) { arg = BigInteger.Zero; } + else if (arg is InteropInterface interop) + { + // We can't send the interopInterface by an script + // We create a syscall in order to detect it and push the item + + dynArgument ??= new DynamicArgumentSyscall(); + script.EmitSysCall(DynamicArgumentSyscall.Hash, dynArgument.Add(() => interop)); + continue; + } + else if (arg is Func onItem) + { + // We create a syscall in order to detect it and push the item + + dynArgument ??= new DynamicArgumentSyscall(); + script.EmitSysCall(DynamicArgumentSyscall.Hash, dynArgument.Add(onItem)); + continue; + } script.EmitPush(arg); } From d0706180d732a76c84f5782b42ccc97456872340 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Toledano Date: Thu, 29 Feb 2024 14:02:07 +0100 Subject: [PATCH 10/11] Testing Syscall --- .../SmartContract.cs | 22 ++++++------- .../TestingApplicationEngine.cs | 32 +++++++++++++++++++ ...icArgumentSyscall.cs => TestingSyscall.cs} | 21 ++++++------ 3 files changed, 53 insertions(+), 22 deletions(-) rename src/Neo.SmartContract.Testing/{DynamicArgumentSyscall.cs => TestingSyscall.cs} (57%) diff --git a/src/Neo.SmartContract.Testing/SmartContract.cs b/src/Neo.SmartContract.Testing/SmartContract.cs index 3a960e0c8..64b502bab 100644 --- a/src/Neo.SmartContract.Testing/SmartContract.cs +++ b/src/Neo.SmartContract.Testing/SmartContract.cs @@ -53,7 +53,7 @@ internal StackItem Invoke(string methodName, params object[] args) { // Compose script - DynamicArgumentSyscall? dynArgument = null; + TestingSyscall? dynArgument = null; using ScriptBuilder script = new(); ConvertArgs(script, args, ref dynArgument); @@ -68,14 +68,14 @@ internal StackItem Invoke(string methodName, params object[] args) return Engine.Execute(script.ToArray(), 0, dynArgument is null ? null : engine => ConfigureEngine(engine, dynArgument)); } - private void ConfigureEngine(ApplicationEngine engine, DynamicArgumentSyscall dynArgument) + private void ConfigureEngine(ApplicationEngine engine, TestingSyscall testingSyscall) { if (engine is not TestingApplicationEngine testEngine) throw new InvalidOperationException(); - testEngine.DynamicArgumentSyscall = dynArgument; + testEngine.TestingSyscall = testingSyscall; } - private void ConvertArgs(ScriptBuilder script, object[] args, ref DynamicArgumentSyscall? dynArgument) + private void ConvertArgs(ScriptBuilder script, object[] args, ref TestingSyscall? testingSyscall) { if (args is null || args.Length == 0) script.Emit(OpCode.NEWARRAY0); @@ -87,12 +87,12 @@ private void ConvertArgs(ScriptBuilder script, object[] args, ref DynamicArgumen if (arg is object[] arg2) { - ConvertArgs(script, arg2, ref dynArgument); + ConvertArgs(script, arg2, ref testingSyscall); continue; } else if (arg is IEnumerable argEnumerable) { - ConvertArgs(script, argEnumerable.ToArray(), ref dynArgument); + ConvertArgs(script, argEnumerable.ToArray(), ref testingSyscall); continue; } @@ -113,16 +113,16 @@ private void ConvertArgs(ScriptBuilder script, object[] args, ref DynamicArgumen // We can't send the interopInterface by an script // We create a syscall in order to detect it and push the item - dynArgument ??= new DynamicArgumentSyscall(); - script.EmitSysCall(DynamicArgumentSyscall.Hash, dynArgument.Add(() => interop)); + testingSyscall ??= new TestingSyscall(); + script.EmitSysCall(TestingSyscall.Hash, testingSyscall.Add((e) => e.Push(interop))); continue; } - else if (arg is Func onItem) + else if (arg is Action onItem) { // We create a syscall in order to detect it and push the item - dynArgument ??= new DynamicArgumentSyscall(); - script.EmitSysCall(DynamicArgumentSyscall.Hash, dynArgument.Add(onItem)); + testingSyscall ??= new TestingSyscall(); + script.EmitSysCall(TestingSyscall.Hash, testingSyscall.Add((e) => onItem(e))); continue; } diff --git a/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs b/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs index f0f81c6a6..be35563ca 100644 --- a/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs +++ b/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs @@ -6,6 +6,7 @@ using Neo.VM; using Neo.VM.Types; using System; +using System.Collections.Generic; namespace Neo.SmartContract.Testing { @@ -20,11 +21,37 @@ internal class TestingApplicationEngine : ApplicationEngine private long PreExecuteInstructionGasConsumed; private bool? BranchPath; + /// + /// Register dynamic argument syscall + /// + static TestingApplicationEngine() + { + var items = typeof(ApplicationEngine) + .GetField("services", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)! + .GetValue(null) as Dictionary; + + InteropDescriptor descriptor = new() + { + Name = TestingSyscall.Name, + Handler = typeof(TestingApplicationEngine).GetMethod(nameof(InvokeTestingSyscall), + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic), + FixedPrice = 0, + RequiredCallFlags = CallFlags.None, + }; + + items?.Add(descriptor.Hash, descriptor); + } + /// /// Testing engine /// public TestEngine Engine { get; } + /// + /// Testing syscall + /// + public TestingSyscall? TestingSyscall { get; set; } = null; + /// /// Override CallingScriptHash /// @@ -55,6 +82,11 @@ public TestingApplicationEngine(TestEngine engine, TriggerType trigger, IVerifia Engine = engine; } + internal void InvokeTestingSyscall(int index) + { + TestingSyscall?.Invoke(this, index); + } + protected override void PreExecuteInstruction(Instruction instruction) { // Cache coverage data diff --git a/src/Neo.SmartContract.Testing/DynamicArgumentSyscall.cs b/src/Neo.SmartContract.Testing/TestingSyscall.cs similarity index 57% rename from src/Neo.SmartContract.Testing/DynamicArgumentSyscall.cs rename to src/Neo.SmartContract.Testing/TestingSyscall.cs index 4351f1e05..db34f01dd 100644 --- a/src/Neo.SmartContract.Testing/DynamicArgumentSyscall.cs +++ b/src/Neo.SmartContract.Testing/TestingSyscall.cs @@ -1,5 +1,4 @@ using Neo.Cryptography; -using Neo.VM.Types; using System; using System.Buffers.Binary; using System.Collections.Generic; @@ -7,14 +6,14 @@ namespace Neo.SmartContract.Testing { - internal class DynamicArgumentSyscall + internal class TestingSyscall { - private readonly List> _actions = new(); + private readonly List> _actions = new(); /// /// Syscall Name /// - public const string Name = $"{nameof(DynamicArgumentSyscall)}.Invoke"; + public const string Name = $"Neo.SmartContract.Testing.Invoke"; /// /// Syscall hash @@ -25,21 +24,21 @@ internal class DynamicArgumentSyscall /// Add action /// /// Action - /// Sycall argument - public int Add(Func action) + /// Action index + public int Add(Action action) { _actions.Add(action); return _actions.Count - 1; } /// - /// Get item + /// Invoke action /// - /// Index - /// Stack item - public StackItem Invoke(int index) + /// Engine + /// Action index + internal void Invoke(TestingApplicationEngine engine, int index) { - return _actions[index](); + _actions[index](engine); } } } From e505ad3ea5549238f3237ad5d68f8ecc013da605 Mon Sep 17 00:00:00 2001 From: Shargon Date: Thu, 29 Feb 2024 14:45:02 +0100 Subject: [PATCH 11/11] Update src/Neo.SmartContract.Testing/TestEngine.cs --- src/Neo.SmartContract.Testing/TestEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs index 09aa8ac9d..d51758fbf 100644 --- a/src/Neo.SmartContract.Testing/TestEngine.cs +++ b/src/Neo.SmartContract.Testing/TestEngine.cs @@ -668,7 +668,7 @@ public StackItem Execute(Script script, int initialPosition = 0, Action /// Clear Transaction Signers /// - public void SetTransactionSigners() + public void ClearTransactionSigners() { Transaction.Signers = System.Array.Empty(); }