-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create Testengine executable #365
Changes from 6 commits
7025642
c0b369e
bd17afc
4c23f48
80335e3
ef71433
f0e7d60
ba806a1
b792b97
8c44910
71b59ff
d172427
9bda72e
3376c2f
ebbc6b4
04d9c97
b361346
4229595
deb289c
850bd5e
6c979b9
ebe33b2
2c1f799
18e4ee9
2a5a9f7
44d7ff2
adc77d2
b83def3
e9875e8
459df19
28d5337
231a6f5
7f77a54
19294ca
0950ff9
0e85e06
4dfd20a
952a751
f47fbc8
5618442
8f129e2
88e916b
d24350b
0c84cc8
de7dee1
3af18e4
1ea9d51
a79b25e
a418ae4
0ea2a09
ab784d2
c8346d2
563f3ab
0ceaf14
dd13f22
c261158
0ac6724
8936b86
92bb0c4
42236fa
979a6f9
b4aac8e
71a3cb2
c08ac2d
0b7545c
99fd474
7870398
53ef46a
04353eb
6f0f118
bea4b4b
1fb01e8
96938b2
c96bc45
5311586
a3d5b42
ec2f1d8
90a33f7
c9840c2
6b509d5
9af8e40
82a51ea
f340a12
211437a
fd27e4e
94ee45b
f591212
1aec9f7
01d545e
6654621
1de1ed2
fc7580a
8b662ee
96bdd7e
0033226
dccde19
2c29e6e
9287447
db367d2
7dded43
02be35f
c2dbad4
4fddf69
2699b79
79f2164
e8c6ed2
1c42c2d
8036c5a
0c1303e
e68accf
7f99692
e64dedd
2ebe4f9
475f173
46849a8
6f31e16
83c4f6b
7fbe253
f20b2c8
f797616
4bfdd89
7445246
9b43aab
11efdde
cd39ea9
92b9e1d
2e330a1
c2ef0ae
6768e34
f0296df
9421daf
138176b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
using Neo; | ||
using Neo.Compiler.MSIL.UnitTests.Utils; | ||
using Neo.Cryptography.ECC; | ||
using Neo.IO.Json; | ||
using Neo.Network.P2P.Payloads; | ||
using Neo.SmartContract; | ||
using Neo.SmartContract.Manifest; | ||
using Neo.VM; | ||
using Neo.VM.Types; | ||
|
||
namespace TestingEngine | ||
{ | ||
public class Engine | ||
{ | ||
private static Engine instance = null; | ||
public static Engine Instance | ||
{ | ||
get | ||
{ | ||
if (instance == null) | ||
{ | ||
instance = new Engine(); | ||
} | ||
return instance; | ||
} | ||
} | ||
|
||
private TestEngine engine = null; | ||
private byte[] PubKey => HexString2Bytes("03ea01cb94bdaf0cd1c01b159d474f9604f4af35a3e2196f6bdfdb33b2aa4961fa"); | ||
|
||
private Engine() | ||
{ | ||
engine = SetupNativeContracts(); | ||
} | ||
|
||
public void SetTestEngine(string path) | ||
{ | ||
engine.AddEntryScript(path); | ||
var manifest = ContractManifest.FromJson(JObject.Parse(engine.ScriptEntry.finalManifest)); | ||
|
||
engine.Snapshot.Contracts.Add(manifest.Hash, new Neo.Ledger.ContractState() | ||
{ | ||
Script = engine.ScriptEntry.finalNEF, | ||
Manifest = manifest, | ||
}); | ||
} | ||
|
||
public JObject Run(string method, StackItem[] args) | ||
{ | ||
engine.GetMethod(method).RunEx(args); | ||
return engine.ToJson(); | ||
} | ||
|
||
private TestEngine SetupNativeContracts() | ||
{ | ||
var block = new Block() | ||
{ | ||
Index = 0, | ||
ConsensusData = new ConsensusData(), | ||
Transactions = new Transaction[0], | ||
Witness = new Witness() | ||
{ | ||
InvocationScript = new byte[0], | ||
VerificationScript = Contract.CreateSignatureRedeemScript(ECPoint.FromBytes(PubKey, ECCurve.Secp256k1)) | ||
}, | ||
NextConsensus = UInt160.Zero, | ||
MerkleRoot = UInt256.Zero, | ||
PrevHash = UInt256.Zero | ||
}; | ||
|
||
TestEngine engine = new TestEngine(TriggerType.Application, block); | ||
((TestSnapshot)engine.Snapshot).SetPersistingBlock(block); | ||
|
||
using (var script = new ScriptBuilder()) | ||
{ | ||
script.EmitSysCall(TestEngine.Native_Deploy); | ||
engine.LoadScript(script.ToArray()); | ||
engine.Execute(); | ||
} | ||
engine.ClearNotifications(); | ||
((TestSnapshot)engine.Snapshot).ClearStorage(); | ||
|
||
return engine; | ||
} | ||
|
||
private static byte[] HexString2Bytes(string str) | ||
{ | ||
if (str.IndexOf("0x") == 0) | ||
str = str.Substring(2); | ||
byte[] outd = new byte[str.Length / 2]; | ||
for (var i = 0; i < str.Length / 2; i++) | ||
{ | ||
outd[i] = byte.Parse(str.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber); | ||
} | ||
return outd; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
using Neo; | ||
using Neo.Compiler.MSIL.UnitTests.Utils; | ||
using Neo.IO.Caching; | ||
using Neo.IO.Json; | ||
using Neo.Ledger; | ||
using Neo.SmartContract; | ||
using Neo.SmartContract.Native; | ||
using Neo.VM; | ||
using Neo.VM.Types; | ||
using System; | ||
using System.Linq; | ||
|
||
namespace TestingEngine | ||
{ | ||
public static class Helper | ||
{ | ||
public static JObject ToJson(this TestEngine testEngine) | ||
{ | ||
var json = new JObject(); | ||
|
||
json["vm_state"] = testEngine.State.ToString(); | ||
json["gas_consumed"] = (new BigDecimal(testEngine.GasConsumed, NativeContract.GAS.Decimals)).ToString(); | ||
json["result_stack"] = testEngine.ResultStack.ToJson(); | ||
json["storage"] = testEngine.Snapshot.Storages.ToJson(); | ||
json["notifications"] = new JArray(testEngine.Notifications.Select(n => n.ToJson())); | ||
json["error"] = testEngine.State.HasFlag(VMState.FAULT) ? GetExceptionMessage(testEngine.FaultException) : null; | ||
|
||
return json; | ||
} | ||
|
||
public static JObject ToJson(this EvaluationStack stack) | ||
{ | ||
return new JArray(stack.Select(p => p.ToJson())); | ||
} | ||
|
||
public static JObject ToJson(this NotifyEventArgs notification) | ||
{ | ||
var json = new JObject(); | ||
json["eventName"] = notification.EventName; | ||
json["scriptHash"] = notification.ScriptHash.ToString(); | ||
json["value"] = notification.State.ToJson(); | ||
return json; | ||
} | ||
|
||
public static JObject ToJson(this DataCache<StorageKey, StorageItem> storage) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will need to initialize the storage, for fake balances for example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm currently working on this |
||
{ | ||
var storageMap = new Map(); | ||
foreach (var storagePair in storage.Seek()) | ||
{ | ||
var key = new ByteString(storagePair.Key.Key); | ||
var value = new ByteString(storagePair.Value.Value); | ||
storageMap[key] = value; | ||
} | ||
return storageMap.ToJson()["value"]; | ||
shargon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private static string GetExceptionMessage(Exception exception) | ||
{ | ||
if (exception == null) return "Engine faulted."; | ||
|
||
if (exception.InnerException != null) | ||
{ | ||
return GetExceptionMessage(exception.InnerException); | ||
} | ||
|
||
return exception.Message; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
using Neo.IO.Json; | ||
using Neo.SmartContract; | ||
using Neo.VM; | ||
using Neo.VM.Types; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
|
||
namespace TestingEngine | ||
{ | ||
class Program | ||
{ | ||
static int Main(string[] args) | ||
{ | ||
JObject result; | ||
if (args.Length >= 2) | ||
{ | ||
result = RunWithMethodName(args[0], args[1], string.Join(" ", args.Skip(2))); | ||
} | ||
else | ||
{ | ||
result = BuildJsonException("One or more arguments are missing"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Show an example? |
||
} | ||
|
||
Console.WriteLine(result); | ||
if (!result.ContainsProperty("vm_state")) | ||
{ | ||
return -1; | ||
} | ||
return 0; | ||
} | ||
|
||
/// <summary> | ||
/// Runs a nef script given a method name and its arguments | ||
/// </summary> | ||
/// <param name="path">Absolute path of the script</param> | ||
/// <param name="method">The name of the targeted method</param> | ||
/// <param name="parameters">Json string representing the arguments of the method</param> | ||
/// <returns>Returns a json with the engine state after executing the script</returns> | ||
public static JObject RunWithMethodName(string path, string methodName, string jsonParams) | ||
{ | ||
try | ||
{ | ||
JArray parameters = new JArray(); | ||
if (jsonParams.Length > 0) | ||
{ | ||
var json = JObject.Parse(jsonParams); | ||
if (json is JArray array) | ||
{ | ||
parameters = array; | ||
} | ||
else | ||
{ | ||
parameters.Insert(0, json); | ||
} | ||
} | ||
|
||
return Run(path, methodName, parameters); | ||
} | ||
catch (Exception e) | ||
{ | ||
return BuildJsonException(e.Message); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Runs the given method from a nef script | ||
/// </summary> | ||
/// <param name="path">Absolute path of the script</param> | ||
/// <param name="method">The name of the targeted method</param> | ||
/// <param name="parameters">Arguments of the method</param> | ||
/// <returns>Returns a json with the engine state after executing the script</returns> | ||
public static JObject Run(string path, string method, JArray parameters) | ||
{ | ||
if (!File.Exists(path)) | ||
{ | ||
return BuildJsonException("File doesn't exists"); | ||
} | ||
if (Path.GetExtension(path).ToLowerInvariant() != ".nef") | ||
{ | ||
return BuildJsonException("Invalid file. A .nef file required."); | ||
} | ||
|
||
try | ||
{ | ||
Engine.Instance.SetTestEngine(path); | ||
var stackParams = GetStackItemParameters(parameters); | ||
return Engine.Instance.Run(method, stackParams); | ||
} | ||
catch (Exception e) | ||
{ | ||
return BuildJsonException(e.Message); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Converts the data in a json array to an array of StackItem | ||
/// </summary> | ||
/// <param name="parameters">json array to be converted</param> | ||
/// <returns>Returns the built StackItem array</returns> | ||
private static StackItem[] GetStackItemParameters(JArray parameters) | ||
{ | ||
var items = new List<StackItem>(); | ||
foreach (JObject param in parameters) | ||
{ | ||
var success = false; | ||
if (param.ContainsProperty("value") && param.ContainsProperty("type")) | ||
{ | ||
try | ||
{ | ||
items.Add(ContractParameter.FromJson(param).ToStackItem()); | ||
success = true; | ||
} | ||
catch { } | ||
} | ||
|
||
if (!success) | ||
{ | ||
// if something went wrong while reading the json, inserts null in this argument position | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. show log or break the execution, it could produce test mistakes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ping |
||
items.Add(StackItem.Null); | ||
} | ||
} | ||
return items.ToArray(); | ||
} | ||
|
||
private static JObject BuildJsonException(string message) | ||
{ | ||
var json = new JObject(); | ||
json["error"] = message; | ||
return json; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<Copyright>2015-2020 The Neo Project</Copyright> | ||
<AssemblyTitle>Neo.TestEngine</AssemblyTitle> | ||
<Version>3.0.0-preview3</Version> | ||
<Authors>The Neo Project</Authors> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp3.1</TargetFramework> | ||
<PackageTags>NEO;Blockchain;Smart Contract</PackageTags> | ||
<PackageProjectUrl>https://github.com/neo-project/neo-devpack-dotnet</PackageProjectUrl> | ||
<PackageLicenseExpression>MIT</PackageLicenseExpression> | ||
<RepositoryType>git</RepositoryType> | ||
<RepositoryUrl>https://github.com/neo-project/neo-devpack-dotnet.git</RepositoryUrl> | ||
<RootNamespace>Neo.TestEngine</RootNamespace> | ||
<Company>The Neo Project</Company> | ||
<Product>TestingEngine</Product> | ||
<Description>TestingEngine</Description> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Neo.Compiler.MSIL\Neo.Compiler.MSIL.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ | |
[assembly: InternalsVisibleTo("Neo.SmartContract.Framework.UnitTests")] | ||
namespace Neo.Compiler.MSIL.UnitTests.Utils | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change namespace? |
||
{ | ||
internal static class NeonTestTool | ||
public static class NeonTestTool | ||
{ | ||
/// <summary> | ||
/// Is not the official script hash, just a unique hash related to the script used for unit test purpose | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
using Neo.Compiler.MSIL.Utils; | ||
using Neo.IO.Json; | ||
using Neo.Network.P2P.Payloads; | ||
using Neo.Persistence; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We agreed to use
gasconsumed
format in json properties