diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0cfcc2ef2..b516f09b8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,6 +28,8 @@ jobs: run: dotnet test tests/Neo.Compiler.CSharp.UnitTests /p:CollectCoverage=true /p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/ - name: Test Neo.SmartContract.Framework.UnitTests run: dotnet test tests/Neo.SmartContract.Framework.UnitTests /p:CollectCoverage=true /p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/lcov /p:MergeWith=${GITHUB_WORKSPACE}/coverage/coverage.json /p:Exclude=\"[Neo.Compiler.CSharp.UnitTests]*\" /p:CoverletOutputFormat=lcov + - name: Test Neo.TestEngine.UnitTests + run: dotnet test tests/Neo.TestEngine.UnitTests /p:CollectCoverage=true /p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/lcov /p:MergeWith=${GITHUB_WORKSPACE}/coverage/coverage.json /p:Exclude=\"[Neo.Compiler.CSharp.UnitTests]*\" /p:CoverletOutputFormat=lcov - name: Coveralls uses: coverallsapp/github-action@master with: diff --git a/.gitignore b/.gitignore index a6b1ef7d9..e4817a7ad 100644 --- a/.gitignore +++ b/.gitignore @@ -250,4 +250,4 @@ paket-files/ # JetBrains Rider .idea/ *.sln.iml -/src/Neo.Compiler.MSIL/Properties/launchSettings.json +**/launchSettings.json diff --git a/neo-devpack-dotnet.sln b/neo-devpack-dotnet.sln index 7a80a9b4a..fac17d435 100644 --- a/neo-devpack-dotnet.sln +++ b/neo-devpack-dotnet.sln @@ -16,6 +16,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{79389FC0-C62 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D5266066-0AFD-44D5-A83E-2F73668A63C8}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.TestEngine", "src\Neo.TestEngine\Neo.TestEngine.csproj", "{53F50B0D-10C6-4064-A60D-F8B2D8883131}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.TestEngine.UnitTests", "tests\Neo.TestEngine.UnitTests\Neo.TestEngine.UnitTests.csproj", "{E9B7DFEA-F911-484F-976A-5CDFF7C9B913}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +46,14 @@ Global {93BEC5CC-BAFF-4389-89E7-84AAFF5D495D}.Debug|Any CPU.Build.0 = Debug|Any CPU {93BEC5CC-BAFF-4389-89E7-84AAFF5D495D}.Release|Any CPU.ActiveCfg = Release|Any CPU {93BEC5CC-BAFF-4389-89E7-84AAFF5D495D}.Release|Any CPU.Build.0 = Release|Any CPU + {53F50B0D-10C6-4064-A60D-F8B2D8883131}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53F50B0D-10C6-4064-A60D-F8B2D8883131}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53F50B0D-10C6-4064-A60D-F8B2D8883131}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53F50B0D-10C6-4064-A60D-F8B2D8883131}.Release|Any CPU.Build.0 = Release|Any CPU + {E9B7DFEA-F911-484F-976A-5CDFF7C9B913}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9B7DFEA-F911-484F-976A-5CDFF7C9B913}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9B7DFEA-F911-484F-976A-5CDFF7C9B913}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9B7DFEA-F911-484F-976A-5CDFF7C9B913}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -52,6 +64,8 @@ Global {FC0CAF5A-30A7-4857-B1A4-486FAAA39E5A} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA} {4C6B120B-99B5-4888-B8D5-45031458DD07} = {D5266066-0AFD-44D5-A83E-2F73668A63C8} {93BEC5CC-BAFF-4389-89E7-84AAFF5D495D} = {D5266066-0AFD-44D5-A83E-2F73668A63C8} + {53F50B0D-10C6-4064-A60D-F8B2D8883131} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA} + {E9B7DFEA-F911-484F-976A-5CDFF7C9B913} = {D5266066-0AFD-44D5-A83E-2F73668A63C8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6DA935E1-C674-4364-B087-F1B511B79215} diff --git a/src/Neo.Compiler.CSharp/CompilationContext.cs b/src/Neo.Compiler.CSharp/CompilationContext.cs index 55830d5c2..77efeb91c 100644 --- a/src/Neo.Compiler.CSharp/CompilationContext.cs +++ b/src/Neo.Compiler.CSharp/CompilationContext.cs @@ -163,6 +163,19 @@ public static CompilationContext CompileSources(string[] sourceFiles, Options op return Compile(sourceFiles, references, options); } + public static CompilationContext CompileSources(string[] sourceFiles, List extraReferences, Options options) + { + List references = new(commonReferences); + foreach (var extraRef in extraReferences) + { + if (!references.Contains(extraRef)) + { + references.Add(extraRef); + } + } + return Compile(sourceFiles, references, options); + } + public static Compilation GetCompilation(string csproj, Options options, out XDocument document) { string folder = Path.GetDirectoryName(csproj)!; diff --git a/src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj b/src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj index 66991944d..a81f22a00 100644 --- a/src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj +++ b/src/Neo.SmartContract.Framework/Neo.SmartContract.Framework.csproj @@ -1,4 +1,4 @@ - + Neo.SmartContract.Framework diff --git a/src/Neo.SmartContract.Template/Neo.SmartContract.Template.csproj b/src/Neo.SmartContract.Template/Neo.SmartContract.Template.csproj index 378937f38..d21c865bb 100644 --- a/src/Neo.SmartContract.Template/Neo.SmartContract.Template.csproj +++ b/src/Neo.SmartContract.Template/Neo.SmartContract.Template.csproj @@ -1,4 +1,4 @@ - + Neo.SmartContract.Template diff --git a/src/Neo.TestEngine/Engine.cs b/src/Neo.TestEngine/Engine.cs new file mode 100644 index 000000000..f62381371 --- /dev/null +++ b/src/Neo.TestEngine/Engine.cs @@ -0,0 +1,421 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.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; + private Transaction? currentTx = null; + public UInt160 Sender => currentTx?.Sender; + private ECPoint PubKey => wallet.DefaultAccount.GetKey().PublicKey; + private TestWallet wallet; + + private Engine() + { + var _ = TestBlockchain.TheNeoSystem; + wallet = new TestWallet(); + engine = SetupNativeContracts(); + } + + public uint Height => NativeContract.Ledger.CurrentIndex(engine.Snapshot); + + public DataCache Snapshot => engine.Snapshot; + + public void Reset() + { + engine = SetupNativeContracts(); + } + + public Engine SetEntryScript(string path) + { + AddSmartContract(path); + return this; + } + + public Engine SetEntryScript(UInt160 contractHash) + { + engine.AddEntryScript(contractHash); + return this; + } + + public Engine SetCallingScript(UInt160 contractHash) + { + engine.callingScriptHash = contractHash; + return this; + } + + public Engine AddSmartContract(TestContract contract) + { + var state = AddSmartContract(contract.nefPath); + contract.buildScript = state; + return this; + } + + private object? AddSmartContract(string path) + { + engine.AddEntryScript(path); + + if (engine.ScriptContext?.Success == true) + { + var hash = TestHelper.GetContractHash(engine.Nef.CheckSum, ContractManifest.FromJson(engine.Manifest).Name); + var snapshot = engine.Snapshot; + + ContractState state; + if (!snapshot.ContainsContract(hash)) + { + DeployContract(engine.ScriptContext); + state = NativeContract.ContractManagement.GetContract(snapshot, hash); + if (state is null) + { + state = new ContractState() + { + Id = snapshot.GetNextAvailableId(), + Hash = hash, + Nef = engine.Nef, + Manifest = ContractManifest.FromJson(engine.Manifest), + }; + snapshot.TryContractAdd(state); + } + } + else + { + state = NativeContract.ContractManagement.GetContract(snapshot, hash); + engine.AddEntryScript(new BuildScript(state.Nef, state.Manifest.ToJson(), hash)); + } + } + return engine.ScriptContext; + } + + private void DeployContract(BuildScript scriptContext) + { + var deploy_method_name = "deploy"; + var deploy_args = new ContractParameter[] + { + new ContractParameter(ContractParameterType.ByteArray) { + Value = scriptContext.Nef.ToArray() + }, + new ContractParameter(ContractParameterType.ByteArray) { + Value = scriptContext.Manifest.ToByteArray(false) + }, + new ContractParameter(ContractParameterType.Any) + }; + + engine.AddEntryScript(NativeContract.ContractManagement.Hash); + byte[] script; + using (ScriptBuilder scriptBuilder = new ScriptBuilder()) + { + scriptBuilder.EmitDynamicCall(NativeContract.ContractManagement.Hash, deploy_method_name, deploy_args); + script = scriptBuilder.ToArray(); + } + var stackItemsArgs = deploy_args.Select(a => a.ToStackItem()).ToArray(); + engine.RunNativeContract(script, deploy_method_name, stackItemsArgs); + + if (engine.State == VMState.FAULT && engine.FaultException.Message?.StartsWith("Contract Already Exists") != true) + { + // deploying a contract already deployed is the only error expected to happen here + throw engine.FaultException; + } + + engine.SetContext(scriptContext); + } + + public Engine IncreaseBlockCount(uint newHeight) + { + var snapshot = engine.Snapshot; + if (snapshot.Blocks().Count <= newHeight) + { + Block newBlock; + Block? lastBlock = null; + if (snapshot.Blocks().Count == 0) + { + newBlock = TestBlockchain.TheNeoSystem.GenesisBlock; + snapshot.AddOrUpdateTransactions(newBlock.Transactions, newBlock.Index); + } + else + { + newBlock = CreateBlock(); + } + + while (snapshot.Blocks().Count <= newHeight) + { + var hash = newBlock.Hash; + var trim = newBlock.Trim(); + snapshot.BlocksAddOrUpdate(hash, trim); + lastBlock = newBlock; + newBlock = CreateBlock(); + } + + snapshot.SetCurrentBlockHash(lastBlock.Index, lastBlock.Hash); + } + return this; + } + + public Engine SetStorage(Dictionary storage) + { + var snapshot = (TestDataCache)engine.InitSnapshot; + if (engine.Snapshot is TestDataCache engineSnapshot) + { + snapshot = engineSnapshot; + } + + if (snapshot is TestDataCache) + { + foreach (var (key, value) in storage) + { + snapshot.AddForTest(key, value); + } + } + snapshot.InnerCommit(); + return this; + } + + public Engine SetSigners(Signer[] signers) + { + if (signers.Length > 0) + { + var newSigners = new List(); + foreach (var signer in signers) + { + newSigners.Add(signer); + wallet.AddSignerAccount(signer.Account); + } + currentTx.Signers = newSigners.ToArray(); + } + return this; + } + + internal void SetTxAttributes(TransactionAttribute[] attributes) + { + currentTx.Attributes = attributes.Where(attr => attr != null).ToArray(); + } + + public Engine AddBlock(TestBlock block) + { + var snapshot = engine.Snapshot; + var result = AddBlock(block.Block); + foreach (var txState in block.Transactions) + { + snapshot.AddOrUpdateTransactionState(txState.Transaction, txState.State); + } + return result; + } + + public Engine AddBlock(Block block) + { + var snapshot = engine.Snapshot; + Block? currentBlock = null; + if (Height < block.Index || snapshot.Blocks().Count == 0) + { + IncreaseBlockCount(block.Index); + currentBlock = engine.Snapshot.GetLastBlock(); + } + else + { + currentBlock = NativeContract.Ledger.GetBlock(snapshot, block.Index); + } + + if (currentBlock != null) + { + var hash = currentBlock.Hash; + currentBlock.Header.Timestamp = block.Header.Timestamp; + + if (currentBlock.Transactions.Length > 0) + { + var tx = currentBlock.Transactions.ToList(); + tx.AddRange(block.Transactions); + currentBlock.Transactions = tx.ToArray(); + } + else + { + currentBlock.Transactions = block.Transactions; + } + + foreach (var tx in block.Transactions) + { + tx.ValidUntilBlock = block.Index + ProtocolSettings.Default.MaxValidUntilBlockIncrement; + } + + var trimmed = currentBlock.Trim(); + snapshot.UpdateChangedBlocks(hash, trimmed.Hash, trimmed); + } + + snapshot.AddOrUpdateTransactions(block.Transactions); + return this; + } + + public JObject Run(string method, ContractParameter[] args) + { + TestDataCache snapshot = null; + if (engine.Snapshot is TestDataCache engineSnapshot) + { + snapshot = engineSnapshot; + } + else if (engine.InitSnapshot is TestDataCache engineInitSnapshot) + { + engine.Snapshot.InnerCommit(); + snapshot = engineInitSnapshot; + } + + if (snapshot is not null) + { + if (snapshot.Blocks().Count == 0) + { + // don't use genesis block as persisting block + IncreaseBlockCount(1); + } + + var lastBlock = snapshot.GetLastBlock(); + if (lastBlock.Index == 0) + { + // don't use genesis block as persisting block + IncreaseBlockCount(1); + lastBlock = snapshot.GetLastBlock(); + } + + engine.PersistingBlock.Header = lastBlock.Header; + engine.PersistingBlock.Transactions = lastBlock.Transactions; + + currentTx.ValidUntilBlock = lastBlock.Index; + snapshot.SetCurrentBlockHash(lastBlock.Index, lastBlock.Hash); + } + + var stackItemsArgs = args.Select(a => a.ToStackItem(engine.ReferenceCounter)).ToArray(); + if (engine.ScriptContext is BuildNative native) + { + byte[] script; + using (ScriptBuilder scriptBuilder = new ScriptBuilder()) + { + scriptBuilder.EmitDynamicCall(native.NativeContract.Hash, method, args); + script = scriptBuilder.ToArray(); + } + engine.RunNativeContract(script, method, stackItemsArgs); + } + else + { + using (ScriptBuilder scriptBuilder = new ScriptBuilder()) + { + scriptBuilder.EmitDynamicCall(engine.EntryScriptHash, method, args); + currentTx.Script = scriptBuilder.ToArray(); + } + engine.ExecuteTestCaseStandard(method, stackItemsArgs); + } + + currentTx.ValidUntilBlock = engine.Snapshot.GetLastBlock().Index + ProtocolSettings.Default.MaxValidUntilBlockIncrement; + currentTx.SystemFee = engine.GasConsumed; + currentTx.NetworkFee = wallet.CalculateNetworkFee(engine.Snapshot, currentTx); + engine.Snapshot.AddOrUpdateTransactionState(currentTx, engine.State); + + return engine.ToJson(); + } + + private TestEngine SetupNativeContracts() + { + currentTx = new Transaction() + { + Attributes = new TransactionAttribute[0], + Script = new byte[0], + Signers = new Signer[] { + new Signer() { + Account = wallet.DefaultAccount.ScriptHash, + Scopes = WitnessScope.CalledByEntry, + AllowedContracts = new UInt160[] { }, + AllowedGroups = new ECPoint[] { }, + Rules = new WitnessRule[] { } + } + }, + Witnesses = new Witness[0], + NetworkFee = 1, + Nonce = 2, + SystemFee = 3, + Version = 4 + }; + var persistingBlock = new Block() + { + Header = new Header() + { + Index = 0 + } + }; + TestEngine engine = new TestEngine(TriggerType.Application, currentTx, new TestDataCache(), persistingBlock: persistingBlock); + + engine.ClearNotifications(); + return engine; + } + + private Block CreateBlock(Block? originBlock = null) + { + TrimmedBlock? trimmedBlock = null; + var blocks = engine.Snapshot.Blocks(); + if (blocks.Count > 0) + { + trimmedBlock = blocks.Last(); + } + + if (trimmedBlock == null) + { + trimmedBlock = TestBlockchain.TheNeoSystem.GenesisBlock.Trim(); + } + + var newBlock = new Block() + { + Header = new Header() + { + Index = trimmedBlock.Index + 1, + Timestamp = trimmedBlock.Header.Timestamp + TestBlockchain.TheNeoSystem.Settings.MillisecondsPerBlock, + Nonce = trimmedBlock.Header.Nonce, + Witness = new Witness() + { + InvocationScript = new byte[0], + VerificationScript = Contract.CreateSignatureRedeemScript(PubKey) + }, + NextConsensus = trimmedBlock.Header.NextConsensus, + MerkleRoot = trimmedBlock.Header.MerkleRoot, + PrevHash = trimmedBlock.Hash + }, + Transactions = new Transaction[0] + }; + + if (originBlock != null) + { + newBlock.Header.Timestamp = originBlock.Header.Timestamp; + } + + return newBlock; + } + } +} diff --git a/src/Neo.TestEngine/Extensions/TestExtensions.cs b/src/Neo.TestEngine/Extensions/TestExtensions.cs new file mode 100644 index 000000000..aaee1cf98 --- /dev/null +++ b/src/Neo.TestEngine/Extensions/TestExtensions.cs @@ -0,0 +1,119 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Linq; +using Array = Neo.VM.Types.Array; + +namespace Neo.TestingEngine +{ + public static class TestExtensions + { + private const byte Prefix_NextAvailableId = 15; + + public static StackItem ToStackItem(this ContractParameter parameter, ReferenceCounter referenceCounter) + { + var stackItem = parameter.ToStackItem(); + return SetReferenceCounter(stackItem, referenceCounter); + } + + private static StackItem SetReferenceCounter(StackItem stackItem, ReferenceCounter referenceCounter) + { + if (stackItem is CompoundType) + { + if (stackItem is Map map) + { + var newStackItem = new Map(referenceCounter); + foreach (var (key, value) in map) + { + newStackItem[key] = value; + } + stackItem = newStackItem; + } + else if (stackItem is Array array) + { + stackItem = new Array(referenceCounter, array.SubItems); + } + else if (stackItem is Struct stackStruct) + { + stackItem = new Struct(referenceCounter, stackStruct); + } + } + + return stackItem; + } + + public static void ContractAdd(this DataCache snapshot, ContractState contract) + { + var key = new KeyBuilder(-1, 8).Add(contract.Hash); + snapshot.Add(key, new StorageItem(contract)); + snapshot.InnerCommit(); + } + + public static bool ContainsContract(this DataCache snapshot, UInt160 hash) + { + return NativeContract.ContractManagement.GetContract(snapshot, hash) != null; + } + + public static void DeleteContract(this DataCache snapshot, UInt160 hash) + { + var contract = NativeContract.ContractManagement.GetContract(snapshot, hash); + if (contract != null) + { + var key = new KeyBuilder(NativeContract.ContractManagement.Id, 8).Add(hash); + snapshot.Delete(key); + } + snapshot.InnerCommit(); + } + + public static void DeployNativeContracts(this DataCache snapshot, Block? persistingBlock = null) + { + persistingBlock ??= new NeoSystem(ProtocolSettings.Default).GenesisBlock; + + var method = typeof(ContractManagement).GetMethod("OnPersist", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var engine = new TestEngine(TriggerType.OnPersist, null, snapshot, persistingBlock); + engine.LoadScript(System.Array.Empty()); + method.Invoke(NativeContract.ContractManagement, new object[] { engine }); + engine.Snapshot.InnerCommit(); + + var method2 = typeof(LedgerContract).GetMethod("PostPersist", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + engine = new TestEngine(TriggerType.OnPersist, null, snapshot, persistingBlock); + engine.LoadScript(System.Array.Empty()); + method2.Invoke(NativeContract.Ledger, new object[] { engine }); + engine.Snapshot.InnerCommit(); + } + + public static bool TryContractAdd(this DataCache snapshot, ContractState contract) + { + var key = NativeContract.ContractManagement.CreateStorageKey(8, contract.Hash); + if (snapshot.Contains(key)) + { + return false; + } + + snapshot.Add(key, new StorageItem(contract)); + snapshot.InnerCommit(); + return true; + } + public static int GetNextAvailableId(this DataCache snapshot) + { + StorageItem item = snapshot.GetAndChange(NativeContract.ContractManagement.CreateStorageKey(Prefix_NextAvailableId)); + item.Add(1); + return NativeContract.ContractManagement.ListContracts(snapshot).ToList().Where(state => state.Id >= 0).Count(); + } + } +} diff --git a/src/Neo.TestEngine/Helper.cs b/src/Neo.TestEngine/Helper.cs new file mode 100644 index 000000000..37dd5c8fd --- /dev/null +++ b/src/Neo.TestEngine/Helper.cs @@ -0,0 +1,163 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Linq; +using Neo.Persistence; +using Neo.SmartContract.Iterators; + +namespace Neo.TestingEngine +{ + public static class Helper + { + public static JObject ToJson(this TestEngine testEngine) + { + var json = new JObject(); + + json["vmstate"] = testEngine.State.ToString(); + json["gasconsumed"] = (new BigDecimal((decimal)testEngine.GasConsumedByLastExecution, NativeContract.GAS.Decimals)).ToString(); + json["resultstack"] = testEngine.ResultStack.ToJson(); + json["executedscripthash"] = testEngine.executedScriptHash.ToString(); + json["callingscripthash"] = testEngine.callingScriptHash?.ToString(); + + json["currentblock"] = testEngine.PersistingBlock.ToSimpleJson(); + if (testEngine.ScriptContainer is Transaction tx) + { + json["transaction"] = tx.ToSimpleJson(testEngine.State); + } + + json["storage"] = testEngine.Snapshot.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 JToken ToJson(this EvaluationStack stack) + { + JArray jarr = new(); + foreach (var item in stack) + { + if (item is InteropInterface interopInterface && interopInterface.GetInterface() is IIterator iterator) + { + var max = 100; + JObject json = item.ToJson(); + JArray array = new(); + while (max > 0 && iterator.Next()) + { + array.Add(iterator.Value(null).ToJson()); + max--; + } + json["iterator"] = array; + json["truncated"] = iterator.Next(); + jarr.Add(json); + } + else + { + jarr.Add(item.ToJson()); + } + } + + return jarr; + } + + public static JToken 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 JToken ToJson(this DataCache storage) + { + var jsonStorage = new JArray(); + // had to filter which data should be returned back, since everything is in the storage now + var newStorage = storage.GetStorageOnly(); + + foreach (var (storageKey, storageValue) in newStorage) + { + var key = new ByteString(storageKey.Key); + StackItem value; + try + { + value = new ByteString(storageValue.Value); + } + catch + { + value = StackItem.Null; + } + + var jsonKey = new JObject(); + jsonKey["id"] = storageKey.Id; + jsonKey["key"] = key.ToJson(); + + var jsonValue = new JObject(); + jsonValue["isconstant"] = false; + jsonValue["value"] = value.ToJson(); + + var storageItem = new JObject(); + storageItem["key"] = jsonKey; + storageItem["value"] = jsonValue; + + jsonStorage.Add(storageItem); + } + return jsonStorage; + } + + public static JToken ToSimpleJson(this Block block) + { + JObject json = new JObject(); + json["hash"] = block.Hash.ToString(); + json["index"] = block.Index; + json["timestamp"] = block.Timestamp; + json["transactions"] = new JArray(block.Transactions.Select(tx => tx.ToSimpleJson())); + return json; + } + + public static JObject ToSimpleJson(this Transaction tx, VMState txState = VMState.NONE) + { + // build a tx with the mutable fields to have a consistent hash between program input and output + var simpleTx = new Transaction() + { + Signers = tx.Signers, + Witnesses = tx.Witnesses, + Attributes = tx.Attributes, + Script = tx.Script, + ValidUntilBlock = tx.ValidUntilBlock + }; + + var json = simpleTx.ToJson(ProtocolSettings.Default); + json["state"] = txState.ToString(); + + return json; + } + + private static string GetExceptionMessage(Exception exception) + { + if (exception == null) return "Engine faulted."; + + if (exception.InnerException != null) + { + return GetExceptionMessage(exception.InnerException); + } + + return exception.Message; + } + } +} + diff --git a/src/Neo.TestEngine/Neo.TestEngine.csproj b/src/Neo.TestEngine/Neo.TestEngine.csproj new file mode 100644 index 000000000..4e1128202 --- /dev/null +++ b/src/Neo.TestEngine/Neo.TestEngine.csproj @@ -0,0 +1,33 @@ + + + + Neo.TestEngine + Exe + Neo.TestEngine + Neo.TestingEngine + enable + NEO;Blockchain;Smart Contract + https://github.com/neo-project/neo-devpack-dotnet + MIT + git + https://github.com/neo-project/neo-devpack-dotnet.git + The Neo Project + TestingEngine + TestingEngine + true + true + neotest + + + + + + + + + + scfx + + + + diff --git a/src/Neo.TestEngine/Program.cs b/src/Neo.TestEngine/Program.cs new file mode 100644 index 000000000..379136e1e --- /dev/null +++ b/src/Neo.TestEngine/Program.cs @@ -0,0 +1,641 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Neo.TestingEngine +{ + public class Program + { + static int Main(string[] args) + { + JObject result = Run(args); + Console.WriteLine(result); + if (!result.ContainsProperty("vmstate")) + { + return -1; + } + return 0; + } + + public static JObject Run(string[] args) + { + if (args.Length == 1) + { + var isFile = Path.GetExtension(args[0]).ToLowerInvariant() == ".json"; + JObject input; + try + { + // verifies if the parameter is a json string + string jsonString; + if (isFile) + { + jsonString = File.ReadAllText(args[0]); + } + else + { + jsonString = args[0]; + } + input = (JObject)JToken.Parse(jsonString); + } + catch + { + // if it isn't, at least one argument is missing + string lastExpectedArg; + if (isFile) + { + lastExpectedArg = "json file with method arguments"; + } + else + { + lastExpectedArg = "method arguments as json"; + } + + return BuildJsonException( + string.Format("One or more arguments are missing\n" + + "Expected arguments: <{0}>", + lastExpectedArg) + ); + } + + return RunWithJson(input); + } + + JObject result; + if (args.Length >= 2) + { + string arg2 = ""; + if (args.Length == 3) + { + arg2 = args[2]; + } + else if (args.Length > 3) + { + arg2 = $"[{string.Join(",", args.Skip(2))}]"; + } + result = RunWithMethodName(args[0], args[1], arg2); + } + else + { + result = BuildJsonException( + "One or more arguments are missing\n" + + "Expected arguments: " + ); + } + + return result; + } + + /// + /// Runs a nef script given a method name and its arguments + /// + /// Absolute path of the script + /// The name of the targeted method + /// Json string representing the arguments of the method + /// Returns a json with the engine state after executing the script + public static JObject RunWithMethodName(string path, string methodName, string jsonParams) + { + try + { + JArray parameters = new JArray(); + if (jsonParams.Length > 0) + { + var json = JToken.Parse(jsonParams); + if (json is JArray array) + { + parameters = array; + } + else + { + parameters.Insert(0, json); + } + } + + var smartContractTestCase = new SmartContractTest(path, methodName, parameters); + return Run(smartContractTestCase); + } + catch (Exception e) + { + return BuildJsonException(e.Message); + } + } + + /// + /// Runs a nef script given a json with test engine fake values for testing. + /// + /// json object with fake values and execution arguments + /// Returns a json with the engine state after executing the script + public static JObject RunWithJson(JObject json) + { + var missigFieldMessage = "Missing field: '{0}'"; + if (!json.ContainsProperty("path") && !json.ContainsProperty("scripthash")) + { + return BuildJsonException(string.Format(missigFieldMessage, "path")); + } + + if (!json.ContainsProperty("method")) + { + return BuildJsonException(string.Format(missigFieldMessage, "method")); + } + + if (!json.ContainsProperty("arguments")) + { + json["arguments"] = new JArray(); + } + + try + { + var methodName = json["method"].AsString(); + var parameters = (JArray)json["arguments"]; + + SmartContractTest smartContractTestCase; + if (json.ContainsProperty("path") && json["path"].AsString().Length > 0) + { + var path = json["path"].AsString(); + smartContractTestCase = new SmartContractTest(path, methodName, parameters); + } + else + { + if (!UInt160.TryParse(json["scripthash"].AsString(), out var scriptHash)) + { + throw new FormatException(GetInvalidTypeMessage("UInt160", "scripthash")); + } + + smartContractTestCase = new SmartContractTest(scriptHash, methodName, parameters); + } + + // fake storage + if (json.ContainsProperty("storage")) + { + smartContractTestCase.storage = GetStorageFromJson(json["storage"]); + } + + // additional contracts that aren't the entry point but can be called during executing + if (json.ContainsProperty("contracts")) + { + smartContractTestCase.contracts = GetContractsFromJson(json["contracts"]); + } + + // set data for previous blocks for better simulation + if (json.ContainsProperty("blocks") && json["blocks"] is JArray blocks) + { + smartContractTestCase.blocks = blocks.Select(b => BlockFromJson(b)).ToArray(); + } + + // set the current heigh + if (json.ContainsProperty("height")) + { + if (!uint.TryParse(json["height"].AsString(), out smartContractTestCase.currentHeight)) + { + throw new FormatException(GetInvalidTypeMessage("uint", "height")); + } + } + + // set data for current tx + if (json.ContainsProperty("currenttx")) + { + smartContractTestCase.currentTx = TxFromJson(json["currenttx"]); + } + + // tx signers + if (json.ContainsProperty("signeraccounts") && json["signeraccounts"] is JArray accounts) + { + smartContractTestCase.signers = accounts.Select(account => SignerFromJson(account)).ToArray(); + } + + // calling script hash + if (json.ContainsProperty("callingscripthash")) + { + UInt160? callingScriptHash = null; + if (json["callingscripthash"] != null && !UInt160.TryParse(json["callingscripthash"].AsString(), out callingScriptHash)) + { + throw new FormatException(GetInvalidTypeMessage("UInt160", "callingscripthash")); + } + + smartContractTestCase.callingScriptHash = callingScriptHash; + } + return Run(smartContractTestCase); + } + catch (Exception e) + { + return BuildJsonException(e.Message); + } + } + + /// + /// Runs the given method from a nef script + /// + /// Object with the informations about the test case + /// Returns a json with the engine state after executing the script + public static JObject Run(SmartContractTest smartContractTest) + { + try + { + if (smartContractTest.storage.Count > 0) + { + Engine.Instance.SetStorage(smartContractTest.storage); + } + + foreach (var contract in smartContractTest.contracts) + { + Engine.Instance.AddSmartContract(contract); + } + + foreach (var block in smartContractTest.blocks.OrderBy(b => b.Block.Index)) + { + Engine.Instance.AddBlock(block); + } + + Engine.Instance.IncreaseBlockCount(smartContractTest.currentHeight); + + if (smartContractTest.currentTx != null) + { + Engine.Instance.SetTxAttributes(smartContractTest.currentTx.Attributes); + } + + if (smartContractTest.nefPath != null) + { + IsValidNefPath(smartContractTest.nefPath); + Engine.Instance.SetEntryScript(smartContractTest.nefPath); + } + else + { + Engine.Instance.SetEntryScript(smartContractTest.scriptHash); + } + + Engine.Instance.SetSigners(smartContractTest.signers); + if (smartContractTest.callingScriptHash != null) + { + Engine.Instance.SetCallingScript(smartContractTest.callingScriptHash); + } + + var stackParams = GetStackItemParameters(smartContractTest.methodParameters); + return Engine.Instance.Run(smartContractTest.methodName, stackParams); + } + catch (Exception e) + { + return BuildJsonException(e.Message); + } + } + + /// + /// Converts the data in a json array to a dictionary of StackItem + /// + /// json array with the map values to be converted + /// Returns the built dictionary + private static Dictionary GetStorageFromJson(JToken jsonStorage) + { + if (!(jsonStorage is JArray storage)) + { + throw new Exception("Expecting an array object in 'storage'"); + } + + var missingFieldMessage = "Missing field '{0}'"; + var items = new Dictionary(); + foreach (JObject pair in storage) + { + if (!pair.ContainsProperty("key")) + { + throw new Exception(string.Format(missingFieldMessage, "key")); + } + var jsonKey = (JObject)pair["key"]; + if (!jsonKey.ContainsProperty("id") || !jsonKey.ContainsProperty("key")) + { + throw new Exception(string.Format(missingFieldMessage, "key")); + } + + if (!pair.ContainsProperty("value")) + { + throw new Exception(string.Format(missingFieldMessage, "value")); + } + var jsonValue = (JObject)pair["value"]; + if (!jsonValue.ContainsProperty("isconstant") || !jsonValue.ContainsProperty("value")) + { + throw new Exception(string.Format(missingFieldMessage, "value")); + } + + var key = (PrimitiveType)ContractParameter.FromJson((JObject)jsonKey["key"]).ToStackItem(); + var value = ContractParameter.FromJson((JObject)jsonValue["value"]).ToStackItem(); + + if (!int.TryParse(jsonKey["id"].AsString(), out var storageKeyId)) + { + throw new FormatException(GetInvalidTypeMessage("int", "storageKeyId")); + } + + var storageKey = new StorageKey() + { + Id = storageKeyId, + Key = key.GetSpan().ToArray() + }; + + StorageItem? storageItem = null; + if (value != StackItem.Null) + { + storageItem = new StorageItem(value.GetSpan().ToArray()); + } + items[storageKey] = storageItem; + } + + return items; + } + + /// + /// Converts the data in a json array to a list of test smart contracts + /// + /// json array with the contracts' paths to be converted + /// Returns a list of smart contracts for test + private static List GetContractsFromJson(JToken jsonContracts) + { + if (!(jsonContracts is JArray contracts)) + { + throw new Exception("Expecting an array object in 'contracts'"); + } + + var items = new List(); + foreach (JObject pair in contracts) + { + if (!pair.ContainsProperty("nef")) + { + throw new Exception("Missing field 'nef'"); + } + + var path = pair["nef"].AsString(); + IsValidNefPath(path); + items.Add(new TestContract(path)); + } + + return items; + } + + /// + /// Converts the data in a json array to an array of StackItem + /// + /// json array to be converted + /// Returns the built StackItem array + private static ContractParameter[] GetStackItemParameters(JArray parameters) + { + var items = new List(); + foreach (JObject param in parameters) + { + var success = false; + if (param.ContainsProperty("value") && param.ContainsProperty("type")) + { + try + { + items.Add(ContractParameter.FromJson(param)); + success = true; + } + catch (Exception e) + { + // if something went wrong while reading the json, log the error + Console.WriteLine(e); + } + } + + if (!success) + { + // if something went wrong while reading the json, inserts null in this argument position + items.Add(new ContractParameter() + { + Type = ContractParameterType.Any, + Value = null + }); + } + } + return items.ToArray(); + } + + private static TestBlock BlockFromJson(JToken blockJson) + { + JObject blockJsonObject = (JObject)blockJson; + var transactions = blockJsonObject["transactions"] as JArray; + + if (!uint.TryParse(blockJsonObject["index"].AsString(), out var blockIndex)) + { + throw new FormatException(GetInvalidTypeMessage("uint", "blockIndex")); + } + if (!ulong.TryParse(blockJsonObject["timestamp"].AsString(), out var blockTimestamp)) + { + throw new FormatException(GetInvalidTypeMessage("ulong", "blockTimestamp")); + } + + UInt256? blockHash; + if (!blockJsonObject.ContainsProperty("hash") || !UInt256.TryParse(blockJsonObject["hash"].AsString(), out blockHash)) + { + blockHash = null; + } + + var txStates = transactions.Select(tx => TxStateFromJson(tx)).ToArray(); + var block = new Block() + { + Header = new Header() + { + Index = blockIndex, + Timestamp = blockTimestamp + }, + Transactions = txStates.Select(tx => tx.Transaction).ToArray() + }; + + return new TestBlock(block, txStates, blockHash); + } + + private static Signer SignerFromJson(JToken signerJson) + { + JObject accountJson = (JObject)signerJson; + if (!UInt160.TryParse(accountJson["account"].AsString(), out var signerAccount)) + { + throw new FormatException(GetInvalidTypeMessage("UInt160", "signerAccount")); + } + + if (!accountJson.ContainsProperty("scopes")) + { + accountJson["scopes"] = WitnessScope.CalledByEntry.ToString(); + } + + var parsedSigner = Signer.FromJson(accountJson); + return new Signer() + { + Account = parsedSigner.Account, + Scopes = parsedSigner.Scopes, + AllowedContracts = parsedSigner.AllowedContracts ?? new UInt160[0], + AllowedGroups = parsedSigner.AllowedGroups ?? new Cryptography.ECC.ECPoint[0], + Rules = parsedSigner.Rules ?? new WitnessRule[0] + }; + } + + private static Transaction TxFromJson(JToken txJson) + { + JObject txJsonObject = (JObject)txJson; + Signer[] accounts; + Witness[] witnesses; + List attributes = new List(); + + if (txJsonObject.ContainsProperty("signers") && txJsonObject["signers"] is JArray signersJson) + { + accounts = signersJson.Select(p => SignerFromJson(p)).ToArray(); + } + else + { + accounts = new Signer[0]; + } + + if (txJsonObject.ContainsProperty("witnesses") && txJsonObject["witnesses"] is JArray witnessesJson) + { + witnesses = witnessesJson.Select(w => new Witness() + { + InvocationScript = Convert.FromBase64String(w["invocation"].AsString()), + VerificationScript = Convert.FromBase64String(w["verification"].AsString()) + }).ToArray(); + } + else + { + witnesses = new Witness[0]; + } + + if (txJsonObject.ContainsProperty("attributes") && txJsonObject["attributes"] is JArray attributesJson) + { + foreach (var attr in attributesJson) + { + var txAttribute = TxAttributeFromJson(attr); + if (txAttribute != null) + { + attributes.Add(txAttribute); + } + } + } + + byte[] script; + if (txJsonObject.ContainsProperty("script")) + { + script = Convert.FromBase64String(txJsonObject["script"].AsString()); + } + else if (attributes.Any(attribute => attribute is OracleResponse oracleAttr)) + { + script = OracleResponse.FixedScript; + } + else + { + script = new byte[0]; + } + + return new Transaction() + { + Script = script, + Signers = accounts, + Witnesses = witnesses, + Attributes = attributes.ToArray() + }; + } + + private static TransactionState TxStateFromJson(JToken txJson) + { + JObject txJsonObject = (JObject)txJson; + Transaction tx = TxFromJson(txJsonObject); + + VMState state; + if (!txJsonObject.ContainsProperty("state")) + { + state = VMState.NONE; + } + else if (!Enum.TryParse(txJsonObject["state"].AsString(), out state)) + { + throw new FormatException(GetInvalidTypeMessage("VMState", "transactionState")); + } + + return new TransactionState() + { + Transaction = tx, + State = state + }; + } + + private static TransactionAttribute? TxAttributeFromJson(JToken txAttributeJson) + { + JObject txAttributeJsonObject = (JObject)txAttributeJson; + if (!txAttributeJsonObject.ContainsProperty("type")) + { + return null; + } + + if (!Enum.TryParse(txAttributeJsonObject["type"].AsString(), out var type)) + { + return null; + } + + switch (type) + { + case TransactionAttributeType.OracleResponse: + + if (!Enum.TryParse(txAttributeJsonObject["code"].AsString(), out var responseCode)) + { + throw new ArgumentException(GetInvalidTypeMessage("OracleResponseCode", "oracleResponseCode")); + } + if (!ulong.TryParse(txAttributeJsonObject["id"].AsString(), out var oracleId)) + { + throw new ArgumentException(GetInvalidTypeMessage("ulong", "oracleResponseId")); + } + + return new OracleResponse() + { + Id = oracleId, + Code = responseCode, + Result = Convert.FromBase64String(txAttributeJsonObject["result"].AsString()) + }; + case TransactionAttributeType.HighPriority: + return new HighPriorityAttribute(); + default: + return null; + } + } + + private static bool IsValidNefPath(string path) + { + if (!File.Exists(path)) + { + throw new Exception("File doesn't exists"); + } + + if (Path.GetExtension(path).ToLowerInvariant() != ".nef") + { + throw new Exception("Invalid file. A .nef file required."); + } + + return true; + } + + private static JObject BuildJsonException(string message) + { + var json = new JObject(); + json["error"] = message; + return json; + } + + private static string GetInvalidTypeMessage(string expectedType, string? argumentId = null) + { + if (argumentId is null) + { + return $"Invalid value were given. Expected {expectedType}"; + } + else + { + return $"Invalid value for {argumentId}. Expected {expectedType}"; + } + } + } +} diff --git a/src/Neo.TestEngine/SmartContractTest.cs b/src/Neo.TestEngine/SmartContractTest.cs new file mode 100644 index 000000000..f3f3dd5d1 --- /dev/null +++ b/src/Neo.TestEngine/SmartContractTest.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using System.Collections.Generic; + +namespace Neo.TestingEngine +{ + public class SmartContractTest + { + public readonly UInt160? scriptHash = null; + public readonly string? nefPath = null; + public readonly string methodName; + public JArray methodParameters; + public Dictionary storage; + public List contracts; + public uint currentHeight = 0; + public Signer[] signers; + public UInt160? callingScriptHash = null; + public TestBlock[] blocks; + public Transaction? currentTx; + + public SmartContractTest(string path, string method, JArray parameters) : this(method, parameters) + { + nefPath = path; + } + + public SmartContractTest(UInt160 scriptHash, string method, JArray parameters) : this(method, parameters) + { + this.scriptHash = scriptHash; + } + + private SmartContractTest(string method, JArray parameters) + { + methodName = method; + methodParameters = parameters; + storage = new Dictionary(); + contracts = new List(); + signers = new Signer[] { }; + blocks = new TestBlock[] { }; + } + } +} diff --git a/src/Neo.TestEngine/TestBlock.cs b/src/Neo.TestEngine/TestBlock.cs new file mode 100644 index 000000000..88f946e7a --- /dev/null +++ b/src/Neo.TestEngine/TestBlock.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; + +namespace Neo.TestingEngine +{ + public class TestBlock + { + internal Block Block { get; } + internal UInt256? Hash { get; } + internal TransactionState[] Transactions { get; } + + public TestBlock(Block block, TransactionState[] txs, UInt256? blockHash = null) + { + Block = block; + Hash = blockHash; + Transactions = txs; + } + } +} diff --git a/src/Neo.TestEngine/TestContract.cs b/src/Neo.TestEngine/TestContract.cs new file mode 100644 index 000000000..5de5e55d0 --- /dev/null +++ b/src/Neo.TestEngine/TestContract.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; + +namespace Neo.TestingEngine +{ + public class TestContract + { + internal string nefPath; + internal object? buildScript = null; + + public TestContract(string path) + { + nefPath = path; + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/BuildNative.cs b/src/Neo.TestEngine/TestUtils/BuildNative.cs new file mode 100644 index 000000000..9d8590d5f --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/BuildNative.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; + +namespace Neo.TestingEngine +{ + class BuildNative : BuildScript + { + public readonly NativeContract NativeContract; + public BuildNative(NativeContract nativeContract) + : this(nativeContract, nativeContract.Nef, nativeContract.Manifest.ToJson()) + { + } + + private BuildNative(NativeContract nativeContract, NefFile nef, JObject manifest) : base(nef, manifest) + { + this.NativeContract = nativeContract; + this.DebugInfo = null; + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/BuildScript.cs b/src/Neo.TestEngine/TestUtils/BuildScript.cs new file mode 100644 index 000000000..45c234c6e --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/BuildScript.cs @@ -0,0 +1,104 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.CodeAnalysis; +using Neo.Compiler; +using Neo.IO; +using Neo.Json; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using System.Collections.Generic; +using System.IO; + +namespace Neo.TestingEngine +{ + public class BuildScript + { + public bool Success => !FromCompilation || (Context != null && Context.Success); + + public UInt160? ScriptHash { get; private set; } + public NefFile Nef { get; protected set; } + public JObject Manifest { get; protected set; } + public JObject? DebugInfo { get; protected set; } + public CompilationContext? Context { get; protected set; } + + private bool FromCompilation { get; set; } + + public BuildScript(NefFile nefFile, JObject manifestJson, UInt160? originHash = null) + { + Nef = nefFile; + Manifest = manifestJson; + + if (originHash is null && nefFile != null) + { + ContractManifest parsedManifest = ContractManifest.FromJson(manifestJson); + originHash = TestHelper.GetContractHash(nefFile.CheckSum, parsedManifest.Name); + } + ScriptHash = originHash; + } + + internal static BuildScript Build(List? references = null, bool debug = true, params string[] files) + { + BuildScript script; + if (files.Length == 1 && Path.GetExtension(files[0]).ToLowerInvariant() == ".nef") + { + var filename = files[0]; + MemoryReader reader = new(File.ReadAllBytes(filename)); + + NefFile neffile = new NefFile(); + neffile.Deserialize(ref reader); + var fileNameManifest = filename.Replace(".nef", ".manifest.json"); + string manifestFile = File.ReadAllText(fileNameManifest); + script = new BuildScript(neffile, (JObject)JToken.Parse(manifestFile)) + { + FromCompilation = false + }; + } + else + { + NefFile? nef = null; + JObject? manifest = null; + JObject? debuginfo = null; + + var options = new Options + { + AddressVersion = ProtocolSettings.Default.AddressVersion, + Debug = debug + }; + + CompilationContext context; + if (references != null && references.Count > 0) + { + context = CompilationContext.CompileSources(files, references, options); + } + else + { + context = CompilationContext.CompileSources(files, options); + } + + if (context.Success) + { + nef = context.CreateExecutable(); + manifest = context.CreateManifest(); + debuginfo = context.CreateDebugInformation(); + } + + script = new BuildScript(nef, manifest) + { + FromCompilation = true, + Context = context, + DebugInfo = debuginfo + }; + } + + return script; + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/Helper.cs b/src/Neo.TestEngine/TestUtils/Helper.cs new file mode 100644 index 000000000..124482d3f --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/Helper.cs @@ -0,0 +1,19 @@ +using Neo.VM; +using Neo.SmartContract; + +namespace Neo.TestingEngine +{ + internal class TestHelper + { + public static UInt160 GetContractHash(uint nefCheckSum, string name) + { + using var sb = new ScriptBuilder(); + sb.Emit(OpCode.ABORT); + sb.EmitPush(Engine.Instance.Sender); + sb.EmitPush(nefCheckSum); + sb.EmitPush(name); + + return sb.ToArray().ToScriptHash(); + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/TestAccount.cs b/src/Neo.TestEngine/TestUtils/TestAccount.cs new file mode 100644 index 000000000..e6eb38b68 --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/TestAccount.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; +using Neo.Wallets; +using System; + +namespace Neo.TestingEngine +{ + class TestAccount : WalletAccount + { + public override bool HasKey => this.key != null; + private readonly KeyPair? key = null; + + public TestAccount(UInt160 scriptHash) : base(scriptHash, ProtocolSettings.Default) { } + public TestAccount(KeyPair privateKey) : base(Contract.CreateSignatureRedeemScript(privateKey.PublicKey).ToScriptHash(), ProtocolSettings.Default) + { + key = privateKey ?? throw new ArgumentNullException(nameof(privateKey)); + Contract = new() + { + Script = Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + } + + public override KeyPair? GetKey() + { + return this.key; + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/TestBlockchain.cs b/src/Neo.TestEngine/TestUtils/TestBlockchain.cs new file mode 100644 index 000000000..17c80f758 --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/TestBlockchain.cs @@ -0,0 +1,274 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; +using System.Linq; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; + +namespace Neo.TestingEngine +{ + public static class TestBlockchain + { + public static readonly NeoSystem TheNeoSystem; + + const byte Prefix_Block = 5; + const byte Prefix_BlockHash = 9; + const byte Prefix_Transaction = 11; + const byte Prefix_CurrentBlock = 12; + + static TestBlockchain() + { + TheNeoSystem = new NeoSystem(ProtocolSettings.Default); + } + + public static void InnerCommit(this DataCache snapshot) + { + bool hasInnerDataCache = true; + var curSnapshot = snapshot; + while (hasInnerDataCache) + { + try + { + curSnapshot.Commit(); + + var cache = curSnapshot.GetType()?.GetField("innerCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(curSnapshot); + if (cache is DataCache innerCache) + { + curSnapshot = innerCache; + } + else + { + hasInnerDataCache = false; + } + } + catch + { + hasInnerDataCache = false; + } + } + } + + public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializable? key = null) + { + var k = new KeyBuilder(contract.Id, prefix); + if (key != null) k = k.Add(key); + return k; + } + + public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, uint value) + { + return new KeyBuilder(contract.Id, prefix).AddBigEndian(value); + } + + public static List<(StorageKey, StorageItem)> GetStorageOnly(this DataCache snapshot) + { + var nativeTokens = new int[] + { + NativeContract.GAS.Id, NativeContract.NEO.Id, NativeContract.ContractManagement.Id, NativeContract.Oracle.Id + }; + + return snapshot.Find().Where(pair => pair.Key.Id >= 0 || nativeTokens.Contains(pair.Key.Id)).ToList(); + } + + public static void BlocksDelete(this DataCache snapshot, UInt256 hash) + { + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, hash)); + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash)); + snapshot.InnerCommit(); + } + + public static void TransactionAdd(this DataCache snapshot, params TransactionState[] txs) + { + foreach (TransactionState tx in txs) + { + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx)); + } + snapshot.InnerCommit(); + } + + public static void BlocksDelete(this DataCache snapshot, UInt256 hash, TrimmedBlock block) + { + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index)); + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash)); + snapshot.InnerCommit(); + } + + public static void BlocksAdd(this DataCache snapshot, UInt256 hash, TrimmedBlock block) + { + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray())); + snapshot.InnerCommit(); + } + + public static void UpdateChangedBlocks(this DataCache snapshot, UInt256 oldHash, UInt256 newHash, TrimmedBlock block) + { + if (snapshot.Contains(NativeContract.Ledger.CreateStorageKey(Prefix_Block, oldHash))) + { + var oldBlock = NativeContract.Ledger.GetBlock(snapshot, oldHash); + snapshot.BlocksDelete(oldHash, oldBlock.Trim()); + } + + if (snapshot.Contains(NativeContract.Ledger.CreateStorageKey(Prefix_Block, newHash))) + { + snapshot.BlocksDelete(newHash, block); + } + snapshot.BlocksAdd(newHash, block); + snapshot.InnerCommit(); + } + + public static TrimmedBlock Trim(this Block block) + { + return new TrimmedBlock + { + Header = new Header() + { + Version = block.Version, + PrevHash = block.PrevHash, + MerkleRoot = block.MerkleRoot, + Nonce = block.Nonce, + Timestamp = block.Timestamp, + Index = block.Index, + NextConsensus = block.NextConsensus, + Witness = block.Witness, + }, + Hashes = block.Transactions.Select(p => p.Hash).ToArray(), + }; + } + + public static Block TryGetBlock(this DataCache snapshot, uint index) + { + return NativeContract.Ledger.GetBlock(snapshot, index); + } + + public static void BlocksAddOrUpdate(this DataCache snapshot, UInt256 hash, TrimmedBlock block) + { + var existingBlock = NativeContract.Ledger.GetBlock(snapshot, block.Index); + if (existingBlock != null) + { + snapshot.UpdateChangedBlocks(existingBlock.Hash, hash, block); + } + else + { + snapshot.BlocksAdd(hash, block); + } + snapshot.InnerCommit(); + } + + public static List Blocks(this DataCache snapshot) + { + var blockKey = NativeContract.Ledger.CreateStorageKey(Prefix_Block); + return snapshot.Find(blockKey.ToArray()) + .Select(item => item.Value.Value.AsSerializable()) + .OrderBy(b => b.Index).ToList(); + } + + public static List BlocksIndex(this DataCache snapshot) + { + var blockKey = NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash); + return snapshot.Find(blockKey.ToArray()) + .Select(item => new UInt256(item.Value.ToArray())) + .ToList(); + } + + public static Block GetLastBlock(this DataCache snapshot) + { + var trim = snapshot.Blocks().Last(); + return NativeContract.Ledger.GetBlock(snapshot, trim.Hash); + } + + public static void SetCurrentBlockHash(this DataCache snapshot, uint index, UInt256 hash) + { + var key = NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock); + var item = snapshot.GetAndChange(key, () => new StorageItem()); + + var hashIndex = new TestHashIndexState(); + var stack = BinarySerializer.Deserialize(item.Value, ExecutionEngineLimits.Default); + hashIndex.FromStackItem(stack); + hashIndex.Hash = hash; + hashIndex.Index = index; + + item.Value = BinarySerializer.Serialize(hashIndex.ToStackItem(null), ExecutionEngineLimits.Default.MaxStackSize); + snapshot.InnerCommit(); + } + + public static void TransactionAddOrUpdate(this DataCache snapshot, params TransactionState[] txs) + { + foreach (TransactionState tx in txs) + { + var key = NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash); + + if (snapshot.Contains(key)) + { + snapshot.Delete(key); + } + snapshot.Add(key, new StorageItem(tx)); + } + snapshot.InnerCommit(); + } + + public static bool ContainsTransaction(this DataCache snapshot, Transaction tx) + { + var key = NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Hash); + return snapshot.Contains(key); + } + + public static void AddOrUpdateTransactions(this DataCache snapshot, Transaction[] txs, int blockIndex = -1) + { + uint index = blockIndex >= 0 ? (uint)blockIndex : NativeContract.Ledger.CurrentIndex(snapshot); + snapshot.AddOrUpdateTransactions(txs, index); + } + + public static void AddOrUpdateTransactions(this DataCache snapshot, Transaction[] txs, uint index) + { + var states = new List(); + foreach (var tx in txs) + { + var transaction = tx; + if (snapshot.ContainsTransaction(tx)) + { + transaction = NativeContract.Ledger.GetTransaction(snapshot, tx.Hash); + } + + var state = new TransactionState() + { + BlockIndex = index, + Transaction = transaction + }; + states.Add(state); + } + + snapshot.TransactionAddOrUpdate(states.ToArray()); + snapshot.InnerCommit(); + } + + public static void AddOrUpdateTransactionState(this DataCache snapshot, Transaction tx, VMState state, int blockIndex = -1) + { + uint index = blockIndex >= 0 ? (uint)blockIndex : NativeContract.Ledger.CurrentIndex(snapshot); + var transaction = tx; + if (snapshot.ContainsTransaction(tx)) + { + transaction = NativeContract.Ledger.GetTransaction(snapshot, tx.Hash); + } + + var txState = new TransactionState() + { + BlockIndex = index, + State = state, + Transaction = tx + }; + snapshot.TransactionAddOrUpdate(new TransactionState[] { txState }); + } + } +} diff --git a/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestDataCache.cs b/src/Neo.TestEngine/TestUtils/TestDataCache.cs similarity index 58% rename from tests/Neo.Compiler.CSharp.UnitTests/Utils/TestDataCache.cs rename to src/Neo.TestEngine/TestUtils/TestDataCache.cs index 462aae98b..b20d0098d 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestDataCache.cs +++ b/src/Neo.TestEngine/TestUtils/TestDataCache.cs @@ -1,16 +1,26 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; using System.Collections.Generic; using System.Linq; -namespace Neo.Compiler.CSharp.UnitTests.Utils +namespace Neo.TestingEngine { public class TestDataCache : DataCache { private readonly Dictionary dict = new(); - public TestDataCache(Block persistingBlock = null) + public TestDataCache(Block? persistingBlock = null) { this.DeployNativeContracts(persistingBlock); } @@ -30,7 +40,7 @@ protected override bool ContainsInternal(StorageKey key) return dict.ContainsKey(key); } - protected override StorageItem GetInternal(StorageKey key) + protected override StorageItem? GetInternal(StorageKey key) { if (!dict.TryGetValue(key, out var value)) { @@ -45,7 +55,7 @@ protected override StorageItem GetInternal(StorageKey key) return dict.Select(u => (u.Key, u.Value)); } - protected override StorageItem TryGetInternal(StorageKey key) + protected override StorageItem? TryGetInternal(StorageKey key) { return dict.TryGetValue(key, out var value) ? value : null; } @@ -54,5 +64,17 @@ protected override void UpdateInternal(StorageKey key, StorageItem value) { dict[key] = value; } + + /// + /// Include a new value to the storage for unit test + /// + public void AddForTest(StorageKey key, StorageItem value) + { + if (Contains(key)) + { + Delete(key); + } + Add(key, value); + } } } diff --git a/src/Neo.TestEngine/TestUtils/TestEngine.cs b/src/Neo.TestEngine/TestUtils/TestEngine.cs new file mode 100644 index 000000000..ee9e45d6f --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/TestEngine.cs @@ -0,0 +1,304 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Neo.Compiler; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Numerics; + +namespace Neo.TestingEngine +{ + public class TestEngine : ApplicationEngine + { + public const long TestGas = 2000_00000000; + + private static readonly List references = new(); + + private long previousGasConsumed = 0; + internal UInt160 executedScriptHash { get; private set; } + internal UInt160 callingScriptHash { get; set; } + public long GasConsumedByLastExecution => GasConsumed - previousGasConsumed; + + public NefFile Nef { get; private set; } + public JObject Manifest { get; private set; } + public JObject DebugInfo { get; private set; } + + internal BuildScript? ScriptContext { get; set; } + internal DataCache InitSnapshot { get; private set; } + + public void ClearNotifications() + { + typeof(ApplicationEngine).GetField("notifications", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.SetValue(this, null); + } + + static TestEngine() + { + try + { + string coreDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!; + references.Add(MetadataReference.CreateFromFile(Path.Combine(coreDir, "System.Runtime.dll"))); + references.Add(MetadataReference.CreateFromFile(Path.Combine(coreDir, "System.Runtime.InteropServices.dll"))); + references.Add(MetadataReference.CreateFromFile(typeof(string).Assembly.Location)); + references.Add(MetadataReference.CreateFromFile(typeof(DisplayNameAttribute).Assembly.Location)); + references.Add(MetadataReference.CreateFromFile(typeof(BigInteger).Assembly.Location)); + + string folder = Path.GetFullPath("../../../../../src/Neo.SmartContract.Framework/"); + string obj = Path.Combine(folder, "obj"); + IEnumerable st = Directory.EnumerateFiles(folder, "*.cs", SearchOption.AllDirectories) + .Where(p => !p.StartsWith(obj)) + .OrderBy(p => p) + .Select(p => CSharpSyntaxTree.ParseText(File.ReadAllText(p), path: p)); + CSharpCompilationOptions options = new(OutputKind.DynamicallyLinkedLibrary); + CSharpCompilation cr = CSharpCompilation.Create(null, st, references, options); + references.Add(cr.ToMetadataReference()); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + public TestEngine(TriggerType trigger = TriggerType.Application, IVerifiable verificable = null, DataCache snapshot = null, Block persistingBlock = null, IDiagnostic diagnostic = null) + : base(trigger, verificable, snapshot, persistingBlock, ProtocolSettings.Default, TestGas, diagnostic) + { + this.InitSnapshot = this.Snapshot; + this.executedScriptHash = UInt160.Zero; + } + + public CompilationContext? AddEntryScript(params string[] files) + { + return AddEntryScript(false, files); + } + + public CompilationContext? AddEntryScript(bool debug = true, params string[] files) + { + ScriptContext = BuildScript.Build(references, debug, files); + SetContext(ScriptContext); + + return ScriptContext.Context; + } + + public CompilationContext? SetContext(BuildScript context) + { + ScriptContext = context; + + if (context.Success) + { + Nef = context.Nef; + Manifest = context.Manifest; + DebugInfo = context.DebugInfo; + Reset(); + } + + return ScriptContext.Context; + } + + public bool AddEntryScript(UInt160 contractHash) + { + var nativeContract = NativeContract.GetContract(contractHash); + if (nativeContract != null) + { + return AddEntryScript(new BuildNative(nativeContract)); + } + + if (!Snapshot.ContainsContract(contractHash)) + { + return false; + } + + var state = NativeContract.ContractManagement.GetContract(Snapshot, contractHash); + + Nef = state.Nef; + Manifest = state.Manifest.ToJson(); + Reset(); + + return true; + } + + public bool AddEntryScript(BuildScript script) + { + var contractHash = TestHelper.GetContractHash(script.Nef.CheckSum, ContractManifest.FromJson(script.Manifest).Name); + var contract = NativeContract.ContractManagement.GetContract(Snapshot, contractHash); + + if (contract != null) + { + Nef = contract.Nef; + Manifest = contract.Manifest.ToJson(); + } + else + { + Nef = script.Nef; + Manifest = script.Manifest; + } + + Reset(); + ScriptContext = script; + return true; + } + + public bool SetCallingScript(UInt160 contractHash) + { + callingScriptHash = CallingScriptHash; + return true; + } + + public void RunNativeContract(byte[] script, string method, StackItem[] parameters, CallFlags flags = CallFlags.All) + { + var rvcount = GetMethodReturnCount(method); + var contractScript = new TestScript(script); + + InvocationStack.Pop(); + var context = CreateContext(contractScript, rvcount, 0); + LoadContext(context); + + var mockedNef = new TestNefFile(script); + ExecuteTestCaseStandard(0, (ushort)rvcount, mockedNef, new ContractManifest(), new StackItem[0]); + } + + public void Reset() + { + this.State = VMState.BREAK; // Required for allow to reuse the same TestEngine + this.InvocationStack.Clear(); + while (this.ResultStack.Count > 0) + { + this.ResultStack.Pop(); + } + if (Nef != null) + { + this.LoadScript(Nef.Script); + // Mock contract + var contextState = CurrentContext.GetState(); + var contractManifest = ContractManifest.FromJson(Manifest); + var contractHash = TestHelper.GetContractHash(Nef.CheckSum, contractManifest.Name); + + contextState.Contract ??= new ContractState { Nef = Nef, Manifest = contractManifest, Hash = contractHash }; + contextState.ScriptHash = contractHash; + } + } + + private int GetMethodEntryOffset(string methodname) + { + if (Manifest is null) return -1; + var methods = Manifest["abi"]["methods"] as JArray; + foreach (var item in methods) + { + var method = item as JObject; + if (method["name"].AsString() == methodname) + return int.Parse(method["offset"].AsString()); + } + return -1; + } + + private int GetMethodReturnCount(string methodname) + { + if (Manifest is null) return -1; + var methods = Manifest["abi"]["methods"] as JArray; + foreach (var item in methods) + { + var method = item as JObject; + if (method["name"].AsString() == methodname) + { + var returntype = method["returntype"].AsString(); + if (returntype == "Null" || returntype == "Void") + return 0; + else + return 1; + } + } + return -1; + } + + public EvaluationStack ExecuteTestCaseStandard(string methodname, params StackItem[] args) + { + return ExecuteTestCaseStandard(methodname, Nef, ContractManifest.FromJson(Manifest), args); + } + + public EvaluationStack ExecuteTestCaseStandard(string methodname, NefFile contract, params StackItem[] args) + { + return ExecuteTestCaseStandard(methodname, contract, ContractManifest.FromJson(Manifest), args); + } + + public EvaluationStack ExecuteTestCaseStandard(string methodname, NefFile contract, ContractManifest manifest, params StackItem[] args) + { + var offset = GetMethodEntryOffset(methodname); + if (offset == -1) throw new Exception("Can't find method : " + methodname); + var rvcount = GetMethodReturnCount(methodname); + if (rvcount == -1) throw new Exception("Can't find method return count : " + methodname); + return ExecuteTestCaseStandard(offset, (ushort)rvcount, contract, manifest, args); + } + + public EvaluationStack ExecuteTestCaseStandard(int offset, ushort rvcount, params StackItem[] args) + { + return ExecuteTestCaseStandard(offset, rvcount, Nef, ContractManifest.FromJson(Manifest), args); + } + + public EvaluationStack ExecuteTestCaseStandard(int offset, ushort rvcount, NefFile contract, ContractManifest manifest, params StackItem[] args) + { + executedScriptHash = EntryScriptHash; + previousGasConsumed = GasConsumed; + var context = InvocationStack.Pop(); + ExecutionContext? callingContext = context; + var callingState = context.GetState(); + + // Mock Calling Script Hash + if (callingScriptHash is not null) + { + callingState.Contract ??= new ContractState() { Hash = callingScriptHash }; + callingState.ScriptHash = callingScriptHash; + } + else if (ScriptContainer is Transaction currentTx) // Mock Signer Script Hash + { + callingState.ScriptHash = currentTx.Signers[0].Account; + } + + context = CreateContext(context.Script, rvcount, offset); + LoadContext(context); + + // Mock contract + var contextState = CurrentContext.GetState(); + contextState.Contract ??= new ContractState() { Nef = contract, Manifest = manifest }; + contextState.ScriptHash = ScriptContext?.ScriptHash; + contextState.CallingContext = callingContext; + + for (var i = args.Length - 1; i >= 0; i--) + this.Push(args[i]); + var initializeOffset = GetMethodEntryOffset("_initialize"); + if (initializeOffset != -1) + { + LoadContext(CurrentContext.Clone(initializeOffset)); + } + while (true) + { + var bfault = (this.State & VMState.FAULT) > 0; + var bhalt = (this.State & VMState.HALT) > 0; + if (bfault || bhalt) break; + Console.WriteLine("op:[" + + this.CurrentContext.InstructionPointer.ToString("X04") + + "]" + + this.CurrentContext.CurrentInstruction?.OpCode); + this.ExecuteNext(); + } + return this.ResultStack; + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/TestHashIndexState.cs b/src/Neo.TestEngine/TestUtils/TestHashIndexState.cs new file mode 100644 index 000000000..89f7e230e --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/TestHashIndexState.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; + +namespace Neo.TestingEngine +{ + public class TestHashIndexState : IInteroperable + { + public UInt256? Hash; + public uint Index; + + internal void FromStackItem(StackItem stackItem) + { + Struct @struct = (Struct)stackItem; + Hash = new UInt256(@struct[0].GetSpan()); + Index = (uint)@struct[1].GetInteger(); + } + + void IInteroperable.FromStackItem(StackItem stackItem) + { + this.FromStackItem(stackItem); + } + + internal StackItem ToStackItem(ReferenceCounter? referenceCounter) + { + return new Struct(referenceCounter) { Hash.ToArray(), Index }; + } + + StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + { + return this.ToStackItem(referenceCounter); + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/TestNefFile.cs b/src/Neo.TestEngine/TestUtils/TestNefFile.cs new file mode 100644 index 000000000..95b5354d1 --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/TestNefFile.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; + +namespace Neo.TestingEngine +{ + internal class TestNefFile : NefFile + { + public TestNefFile(byte[] script) : base() + { + Script = script; + Compiler = "mock"; + Tokens = new MethodToken[0]; + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/TestScript.cs b/src/Neo.TestEngine/TestUtils/TestScript.cs new file mode 100644 index 000000000..8dd222c75 --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/TestScript.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM; + +namespace Neo.TestingEngine +{ + class TestScript : Script + { + public TestScript(byte[] script) : base(script) + { + } + } +} diff --git a/src/Neo.TestEngine/TestUtils/TestWallet.cs b/src/Neo.TestEngine/TestUtils/TestWallet.cs new file mode 100644 index 000000000..9fa76e642 --- /dev/null +++ b/src/Neo.TestEngine/TestUtils/TestWallet.cs @@ -0,0 +1,114 @@ +// Copyright (C) 2015-2022 The Neo Project. +// +// The Neo.Compiler.CSharp is free software distributed under the MIT +// software license, see the accompanying file LICENSE in the main directory +// of the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Collections.Generic; + +namespace Neo.TestingEngine +{ + public class TestWallet : NEP6Wallet + { + private Dictionary accounts; + public WalletAccount DefaultAccount { get; private set; } + + public TestWallet(UInt160? scriptHash = null) : base("", "", ProtocolSettings.Default, "TestWallet") + { + this.accounts = new Dictionary(); + if (scriptHash == null) + { + // mock an account + var mockedPrivateKey = "a8639cbc8dc867fab51487c1ff0565600999ac73136c95454309f4883854efba".HexToBytes(); + DefaultAccount = CreateAccount(mockedPrivateKey); + } + else + { + DefaultAccount = CreateAccount(scriptHash); + } + } + + public override bool ChangePassword(string oldPassword, string newPassword) + { + return false; + } + + public override bool Contains(UInt160 scriptHash) + { + return this.accounts.ContainsKey(scriptHash); + } + + public override WalletAccount CreateAccount(byte[] privateKey) + { + if (privateKey is null) throw new ArgumentNullException(nameof(privateKey)); + KeyPair key = new(privateKey); + if (key.PublicKey.IsInfinity) throw new ArgumentException(null, nameof(privateKey)); + + var account = new TestAccount(key); + AddAccount(account); + return account; + } + + public override WalletAccount CreateAccount(Contract contract, KeyPair? key = null) + { + throw new NotImplementedException(); + } + + public override WalletAccount CreateAccount(UInt160 scriptHash) + { + var account = new TestAccount(scriptHash); + this.accounts[scriptHash] = account; + return account; + } + + private void AddAccount(TestAccount account) + { + if (!accounts.ContainsKey(account.ScriptHash)) + { + accounts[account.ScriptHash] = account; + } + } + + public override bool DeleteAccount(UInt160 scriptHash) + { + if (!this.accounts.ContainsKey(scriptHash)) + { + return false; + } + return accounts.Remove(scriptHash); + } + + public override WalletAccount? GetAccount(UInt160 scriptHash) + { + if (!this.accounts.ContainsKey(scriptHash)) + { + return null; + } + return accounts[scriptHash]; + } + + public override IEnumerable GetAccounts() + { + return this.accounts.Values; + } + + public override bool VerifyPassword(string password) + { + return true; + } + + internal void AddSignerAccount(UInt160 scriptHash) + { + // mock for calculating network fee + this.accounts[scriptHash] = DefaultAccount; + } + } +} diff --git a/tests/Neo.Compiler.CSharp.UnitTests/Neo.Compiler.CSharp.UnitTests.csproj b/tests/Neo.Compiler.CSharp.UnitTests/Neo.Compiler.CSharp.UnitTests.csproj index 54c951ca3..b0477f90c 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/Neo.Compiler.CSharp.UnitTests.csproj +++ b/tests/Neo.Compiler.CSharp.UnitTests/Neo.Compiler.CSharp.UnitTests.csproj @@ -1,4 +1,4 @@ - + Neo.Compiler.CSharp.UnitTests @@ -9,6 +9,7 @@ scfx + diff --git a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Event.cs b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Event.cs index fbe68c5ad..895c5a355 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Event.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Event.cs @@ -12,10 +12,10 @@ public class Contract_Event : SmartContract.Framework.SmartContract [DisplayName("transfer")] public static event Action Transferred; - public static void Main(string method, object[] args) - { + public static void Main(string method, object[] args) + { MyStaticVar1 = 1; - MyStaticVar2 = true; + MyStaticVar2 = true; } } } diff --git a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Initializer.cs b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Initializer.cs index 291471c5f..db35d573e 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Initializer.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Initializer.cs @@ -5,8 +5,8 @@ namespace Neo.Compiler.CSharp.UnitTests.TestClasses { public class Contract_Initializer : SmartContract.Framework.SmartContract { - public class data - { + public class data + { public int A = 1; public int B = 2; } @@ -19,10 +19,10 @@ public int sum() public int sum1(int a, int b) { - var x = new data - { + var x = new data + { A = a, - B = b + B = b }; return x.A + x.B; } @@ -30,8 +30,8 @@ public int sum1(int a, int b) public int sum2(int a, int b) { var x = new data(); - x.A = a; - x.B = b; + x.A = a; + x.B = b; return x.A + x.B; } } diff --git a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Interfaces.cs b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Interfaces.cs index af68bce8f..70d578a83 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Interfaces.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Interfaces.cs @@ -1,48 +1,48 @@ -using Neo.SmartContract.Framework.Services; -using System.Numerics; +using Neo.SmartContract.Framework.Services; +using System.Numerics; namespace Neo.Compiler.CSharp.UnitTests.TestClasses -{ - interface IStorageMap - { - TValue this[TKey account] { get; set; } - } - - class AccountStorage : IStorageMap - { - readonly StorageMap map; - - public AccountStorage() - { - map = new StorageMap(Storage.CurrentContext, nameof(AccountStorage)); - } - - public BigInteger this[UInt160 account] - { - get => (BigInteger)map.Get(account); - set => map.Put(account, value); - } +{ + interface IStorageMap + { + TValue this[TKey account] { get; set; } + } + + class AccountStorage : IStorageMap + { + readonly StorageMap map; + + public AccountStorage() + { + map = new StorageMap(Storage.CurrentContext, nameof(AccountStorage)); + } + + public BigInteger this[UInt160 account] + { + get => (BigInteger)map.Get(account); + set => map.Put(account, value); + } } public class Contract_Interfaces : SmartContract.Framework.SmartContract { - AccountStorage accountStorage = new(); - IStorageMap Accounts => accountStorage; - - public void TransferAll(UInt160 from, UInt160 to) - { - Accounts[to] = Accounts[from] + Accounts[to]; - Accounts[from] = 0; - } - - public void SetValue(UInt160 addr, BigInteger amount) - { - Accounts[addr] = amount; - } - - public BigInteger GetValue(UInt160 addr) - { - return Accounts[addr]; + AccountStorage accountStorage = new(); + IStorageMap Accounts => accountStorage; + + public void TransferAll(UInt160 from, UInt160 to) + { + Accounts[to] = Accounts[from] + Accounts[to]; + Accounts[from] = 0; + } + + public void SetValue(UInt160 addr, BigInteger amount) + { + Accounts[addr] = amount; + } + + public BigInteger GetValue(UInt160 addr) + { + return Accounts[addr]; } } } diff --git a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Partial.2.cs b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Partial.2.cs index ea73b5f6f..83d8b9089 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Partial.2.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/TestClasses/Contract_Partial.2.cs @@ -1,6 +1,6 @@ namespace Neo.Compiler.CSharp.UnitTests.TestClasses { - public partial class Contract_Partial + public partial class Contract_Partial { public static int test2() { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest1.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest1.cs index 009fdeae5..a67130894 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest1.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest1.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using System.Linq; using System.Text; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Attributes.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Attributes.cs index 0bc80e986..d62853abb 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Attributes.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Attributes.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Event.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Event.cs index 323900b28..faebf86d6 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Event.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Event.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.Json; using System; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Safe.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Safe.cs index a135029ae..fd17502f0 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Safe.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ABI_Safe.cs @@ -1,6 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; using Neo.Json; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Array.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Array.cs index 6c3de3813..1e9f8c278 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Array.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Array.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; using System.Linq; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Attribute.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Attribute.cs index 5894d39f3..ee59eb284 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Attribute.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Attribute.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_BigInteger.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_BigInteger.cs index 0563d3c5a..28dd041e0 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_BigInteger.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_BigInteger.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ByteArrayAssignment.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ByteArrayAssignment.cs index cf8c05c8b..387e85829 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ByteArrayAssignment.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ByteArrayAssignment.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_CheckedUnchecked.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_CheckedUnchecked.cs index 322a17f25..49d2cc0be 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_CheckedUnchecked.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_CheckedUnchecked.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Concat.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Concat.cs index c482f7744..157520432 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Concat.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Concat.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ContractCall.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ContractCall.cs index 9a29c5e8f..ea4f9fd86 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ContractCall.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ContractCall.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.VM; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Debug.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Debug.cs index 873ee52c4..b2ccd40c2 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Debug.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Debug.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DebugInfo.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DebugInfo.cs index e831e11d7..9081b2702 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DebugInfo.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_DebugInfo.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.Json; using Neo.SmartContract; using System.Linq; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Delegate.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Delegate.cs index 8fa97792f..49238b2e1 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Delegate.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Delegate.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Extensions.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Extensions.cs index f3e8817c8..9df266264 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Extensions.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Extensions.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Foreach.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Foreach.cs index c1ec8ac14..6acb9689e 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Foreach.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Foreach.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; using Neo.VM.Types; using System.Numerics; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_GoTo.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_GoTo.cs index 18c415b31..6e5b12264 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_GoTo.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_GoTo.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Initializer.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Initializer.cs index 99636cb63..a5e94d385 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Initializer.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Initializer.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Instance.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Instance.cs index 94f0b5ceb..9342c5efc 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Instance.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Instance.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_IntegerParse.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_IntegerParse.cs index ba7dd958d..efe463a2d 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_IntegerParse.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_IntegerParse.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Interfaces.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Interfaces.cs index 976b8c8ef..4080b0385 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Interfaces.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Interfaces.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Invoke.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Invoke.cs index 2bc9f3a75..c5fe2e55f 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Invoke.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Invoke.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Math.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Math.cs index 22029cda4..62cc0846c 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Math.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Math.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_NULL.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_NULL.cs index 14e5aa8d9..a476f8ddf 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_NULL.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_NULL.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.VM.Types; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_NativeContracts.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_NativeContracts.cs index 66d5586f3..43304aac1 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_NativeContracts.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_NativeContracts.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_OnDeployment.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_OnDeployment.cs index 01f495a40..71f711acb 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_OnDeployment.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_OnDeployment.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.Json; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Params.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Params.cs index c64e9f59e..e093ecf24 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Params.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Params.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Polymorphism.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Polymorphism.cs index 4f82306fc..d9e6cd803 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Polymorphism.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Polymorphism.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Property.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Property.cs index 40179dde6..959d6706c 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Property.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Property.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.Compiler.CSharp.UnitTests { diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Returns.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Returns.cs index b908e03ee..bb92381a5 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Returns.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Returns.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Shift.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Shift.cs index 6afec514d..fa9281d85 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Shift.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Shift.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.SmartContract; using System; using System.Collections.Generic; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticByteArray.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticByteArray.cs index bf1db29c1..8cc222c3a 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticByteArray.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticByteArray.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticVar.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticVar.cs index f280407c0..73e651868 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticVar.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_StaticVar.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; using System; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Switch.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Switch.cs index b7b332087..e0aa72aee 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Switch.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Switch.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_TryCatch.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_TryCatch.cs index e85fb0185..b97a88000 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_TryCatch.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_TryCatch.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using System; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Tuple.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Tuple.cs index f3ce03b16..2650705e4 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Tuple.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Tuple.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; using System.Linq; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_TypeConvert.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_TypeConvert.cs index 4a64b1971..20259776c 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_TypeConvert.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_TypeConvert.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; namespace Neo.Compiler.CSharp.UnitTests diff --git a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Types.cs b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Types.cs index f5a4c0f47..76cb5cdfb 100644 --- a/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Types.cs +++ b/tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Types.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.IO; using Neo.Json; using Neo.VM; diff --git a/tests/Neo.Compiler.CSharp.UnitTests/Utils/Extensions.cs b/tests/Neo.Compiler.CSharp.UnitTests/Utils/Extensions.cs deleted file mode 100644 index 849b0eeb2..000000000 --- a/tests/Neo.Compiler.CSharp.UnitTests/Utils/Extensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using Neo.SmartContract; -using System; - -namespace Neo.Compiler.CSharp.UnitTests.Utils -{ - public static class Extensions - { - public static void ContractAdd(this DataCache snapshot, ContractState contract) - { - var key = new KeyBuilder(-1, 8).Add(contract.Hash); - snapshot.Add(key, new StorageItem(contract)); - } - - public static void DeployNativeContracts(this DataCache snapshot, Block persistingBlock = null) - { - persistingBlock ??= new NeoSystem(ProtocolSettings.Default).GenesisBlock; - - var method = typeof(SmartContract.Native.ContractManagement).GetMethod("OnPersist", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - var engine = new TestEngine(TriggerType.OnPersist, null, snapshot, persistingBlock); - engine.LoadScript(Array.Empty()); - method.Invoke(SmartContract.Native.NativeContract.ContractManagement, new object[] { engine }); - engine.Snapshot.Commit(); - - method = typeof(SmartContract.Native.LedgerContract).GetMethod("PostPersist", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - engine = new TestEngine(TriggerType.OnPersist, null, snapshot, persistingBlock); - engine.LoadScript(Array.Empty()); - method.Invoke(SmartContract.Native.NativeContract.Ledger, new object[] { engine }); - engine.Snapshot.Commit(); - } - } -} diff --git a/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestEngine.cs b/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestEngine.cs deleted file mode 100644 index fddd7aab7..000000000 --- a/tests/Neo.Compiler.CSharp.UnitTests/Utils/TestEngine.cs +++ /dev/null @@ -1,171 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Neo.Json; -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using Neo.SmartContract; -using Neo.VM; -using Neo.VM.Types; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Numerics; - -namespace Neo.Compiler.CSharp.UnitTests.Utils -{ - public class TestEngine : ApplicationEngine - { - public const long TestGas = 2000_00000000; - - private static readonly List references = new(); - - public NefFile Nef { get; private set; } - public JObject Manifest { get; private set; } - public JObject DebugInfo { get; private set; } - - static TestEngine() - { - string coreDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!; - references.Add(MetadataReference.CreateFromFile(Path.Combine(coreDir, "System.Runtime.dll"))); - references.Add(MetadataReference.CreateFromFile(Path.Combine(coreDir, "System.Runtime.InteropServices.dll"))); - references.Add(MetadataReference.CreateFromFile(typeof(string).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(DisplayNameAttribute).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(BigInteger).Assembly.Location)); - string folder = Path.GetFullPath("../../../../../src/Neo.SmartContract.Framework/"); - string obj = Path.Combine(folder, "obj"); - IEnumerable st = Directory.EnumerateFiles(folder, "*.cs", SearchOption.AllDirectories) - .Where(p => !p.StartsWith(obj)) - .OrderBy(p => p) - .Select(p => CSharpSyntaxTree.ParseText(File.ReadAllText(p), path: p)); - CSharpCompilationOptions options = new(OutputKind.DynamicallyLinkedLibrary); - CSharpCompilation cr = CSharpCompilation.Create(null, st, references, options); - references.Add(cr.ToMetadataReference()); - } - - public TestEngine(TriggerType trigger = TriggerType.Application, IVerifiable verificable = null, DataCache snapshot = null, Block persistingBlock = null, IDiagnostic diagnostic = null) - : base(trigger, verificable, snapshot, persistingBlock, ProtocolSettings.Default, TestGas, diagnostic) - { - } - - public CompilationContext AddEntryScript(params string[] files) - { - return AddEntryScript(true, files); - } - - public CompilationContext AddEntryScript(bool debug = true, params string[] files) - { - CompilationContext context = CompilationContext.Compile(files, references, new Options - { - AddressVersion = ProtocolSettings.Default.AddressVersion, - Debug = debug, - }); - if (context.Success) - { - Nef = context.CreateExecutable(); - Manifest = context.CreateManifest(); - DebugInfo = context.CreateDebugInformation(); - Reset(); - } - return context; - } - - public void Reset() - { - this.State = VMState.BREAK; // Required for allow to reuse the same TestEngine - this.InvocationStack.Clear(); - while (this.ResultStack.Count > 0) - { - this.ResultStack.Pop(); - } - if (Nef != null) - { - this.LoadScript(Nef.Script); - // Mock contract - var contextState = CurrentContext.GetState(); - contextState.Contract ??= new ContractState { Nef = Nef }; - } - } - - private int GetMethodEntryOffset(string methodname) - { - if (Manifest is null) return -1; - var methods = Manifest["abi"]["methods"] as JArray; - foreach (var item in methods) - { - var method = item as JObject; - if (method["name"].AsString() == methodname) - return int.Parse(method["offset"].AsString()); - } - return -1; - } - - private int GetMethodReturnCount(string methodname) - { - if (Manifest is null) return -1; - var methods = Manifest["abi"]["methods"] as JArray; - foreach (var item in methods) - { - var method = item as JObject; - if (method["name"].AsString() == methodname) - { - var returntype = method["returntype"].AsString(); - if (returntype == "Null" || returntype == "Void") - return 0; - else - return 1; - } - } - return -1; - } - - public EvaluationStack ExecuteTestCaseStandard(string methodname, params StackItem[] args) - { - return ExecuteTestCaseStandard(methodname, Nef, args); - } - - public EvaluationStack ExecuteTestCaseStandard(string methodname, NefFile contract, params StackItem[] args) - { - var offset = GetMethodEntryOffset(methodname); - if (offset == -1) throw new Exception("Can't find method : " + methodname); - var rvcount = GetMethodReturnCount(methodname); - if (rvcount == -1) throw new Exception("Can't find method return count : " + methodname); - return ExecuteTestCaseStandard(offset, (ushort)rvcount, contract, args); - } - - public EvaluationStack ExecuteTestCaseStandard(int offset, ushort rvcount, params StackItem[] args) - { - return ExecuteTestCaseStandard(offset, rvcount, Nef, args); - } - - public EvaluationStack ExecuteTestCaseStandard(int offset, ushort rvcount, NefFile contract, params StackItem[] args) - { - var context = InvocationStack.Pop(); - context = CreateContext(context.Script, rvcount, offset); - LoadContext(context); - // Mock contract - var contextState = CurrentContext.GetState(); - contextState.Contract ??= new ContractState() { Nef = contract }; - for (var i = args.Length - 1; i >= 0; i--) - this.Push(args[i]); - var initializeOffset = GetMethodEntryOffset("_initialize"); - if (initializeOffset != -1) - { - LoadContext(CurrentContext.Clone(initializeOffset)); - } - while (true) - { - var bfault = (this.State & VMState.FAULT) > 0; - var bhalt = (this.State & VMState.HALT) > 0; - if (bfault || bhalt) break; - Console.WriteLine("op:[" + - this.CurrentContext.InstructionPointer.ToString("X04") + - "]" + - this.CurrentContext.CurrentInstruction.OpCode); - this.ExecuteNext(); - } - return this.ResultStack; - } - } -} diff --git a/tests/Neo.SmartContract.Framework.UnitTests/AttributeTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/AttributeTest.cs index db715e199..51f1bd1bf 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/AttributeTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/AttributeTest.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.TestingEngine; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/HelperTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/HelperTest.cs index 3573ff502..16b62790d 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/HelperTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/HelperTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; using Neo.VM.Types; using System.Collections.Generic; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/ListTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/ListTest.cs index 8d240abd3..2fffc1456 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/ListTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/ListTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.Json; using Neo.VM; using Neo.VM.Types; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/ManifestExtraTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/ManifestExtraTest.cs index 7df6f52b4..dfb4e6870 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/ManifestExtraTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/ManifestExtraTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.SmartContract.Framework.UnitTests { diff --git a/tests/Neo.SmartContract.Framework.UnitTests/MapTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/MapTest.cs index de270fbf1..61bba9aa5 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/MapTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/MapTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; using Neo.VM.Types; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Neo.SmartContract.Framework.UnitTests.csproj b/tests/Neo.SmartContract.Framework.UnitTests/Neo.SmartContract.Framework.UnitTests.csproj index 7604ba567..a55a826c3 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Neo.SmartContract.Framework.UnitTests.csproj +++ b/tests/Neo.SmartContract.Framework.UnitTests/Neo.SmartContract.Framework.UnitTests.csproj @@ -1,4 +1,4 @@ - + Neo.SmartContract.Framework.UnitTests @@ -9,6 +9,7 @@ scfx + diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/BlockchainTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/BlockchainTest.cs index 0f71d5313..3e6634828 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/BlockchainTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/BlockchainTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/ContractTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/ContractTest.cs index 2c5e6dd1e..fe7abc21d 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/ContractTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/ContractTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Manifest; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/CryptoTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/CryptoTest.cs index 597953f00..5f22172c3 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/CryptoTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/CryptoTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.Cryptography; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/ExecutionEngineTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/ExecutionEngineTest.cs index 0f260f91d..44409d9cd 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/ExecutionEngineTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/ExecutionEngineTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/JsonTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/JsonTest.cs index b97e3fdf9..39b685530 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/JsonTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/JsonTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; using Neo.VM.Types; using System.Text; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/NativeTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/NativeTest.cs index 0550ffd20..8da8214a2 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/NativeTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/NativeTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.Cryptography.ECC; using Neo.Network.P2P.Payloads; using Neo.VM; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/PointerTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/PointerTest.cs index 74f56cbd3..f89d4d506 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/PointerTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/PointerTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; using Neo.VM.Types; using System.Numerics; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/RuntimeTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/RuntimeTest.cs index fcd89d192..071a977b5 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/RuntimeTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/RuntimeTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/StaticStorageMapTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/StaticStorageMapTest.cs index 219f31dbb..efa1e5cc6 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/StaticStorageMapTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/StaticStorageMapTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM; namespace Neo.SmartContract.Framework.UnitTests.Services diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/StdLibTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/StdLibTest.cs index e091eb1e8..f83a26323 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/StdLibTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/StdLibTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.SmartContract.Manifest; using Neo.VM; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/Services/StorageTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/Services/StorageTest.cs index 024e2e377..d447a9292 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/Services/StorageTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/Services/StorageTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; using System; using System.Linq; diff --git a/tests/Neo.SmartContract.Framework.UnitTests/StringTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/StringTest.cs index 2d63d2dc7..33b071c93 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/StringTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/StringTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.VM.Types; namespace Neo.SmartContract.Framework.UnitTests diff --git a/tests/Neo.SmartContract.Framework.UnitTests/SupportedStandardsTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/SupportedStandardsTest.cs index 80e0634d8..68dc443ad 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/SupportedStandardsTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/SupportedStandardsTest.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; namespace Neo.SmartContract.Framework.UnitTests { diff --git a/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_Native.cs b/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_Native.cs index bef5beba8..4d12666c0 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_Native.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_Native.cs @@ -75,3 +75,4 @@ public static bool Policy_IsBlocked(UInt160 account) } } } + diff --git a/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_StdLib.cs b/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_StdLib.cs index 273ac678a..abf7e98ba 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_StdLib.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/TestClasses/Contract_StdLib.cs @@ -47,13 +47,13 @@ public static string itoa(int value, int @base) public static int memoryCompare(ByteString str1, ByteString str2) { return StdLib.MemoryCompare(str1, str2); - } - + } + public static int memorySearch1(ByteString mem, ByteString value) { return StdLib.MemorySearch(mem, value); - } - + } + public static int memorySearch2(ByteString mem, ByteString value, int start) { return StdLib.MemorySearch(mem, value, start); @@ -62,13 +62,13 @@ public static int memorySearch2(ByteString mem, ByteString value, int start) public static int memorySearch3(ByteString mem, ByteString value, int start, bool backward) { return StdLib.MemorySearch(mem, value, start, backward); - } - + } + public static string[] stringSplit1(string str, string separator) { return StdLib.StringSplit(str, separator); - } - + } + public static string[] stringSplit2(string str, string separator, bool removeEmptyEntries) { return StdLib.StringSplit(str, separator, removeEmptyEntries); diff --git a/tests/Neo.SmartContract.Framework.UnitTests/UIntTest.cs b/tests/Neo.SmartContract.Framework.UnitTests/UIntTest.cs index 4f776d15e..e50946226 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/UIntTest.cs +++ b/tests/Neo.SmartContract.Framework.UnitTests/UIntTest.cs @@ -1,6 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Compiler.CSharp.UnitTests.Utils; +using Neo.TestingEngine; using Neo.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Neo.SmartContract.Framework.UnitTests { diff --git a/tests/Neo.TestEngine.UnitTests/Neo.TestEngine.UnitTests.csproj b/tests/Neo.TestEngine.UnitTests/Neo.TestEngine.UnitTests.csproj new file mode 100644 index 000000000..a6fbe710c --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/Neo.TestEngine.UnitTests.csproj @@ -0,0 +1,33 @@ + + + + Neo.TestEngine.UnitTests + + + + + + + + + + + neo + + + + + + + + PreserveNewest + + + + diff --git a/tests/Neo.TestEngine.UnitTests/TestClasses/Contract1.cs b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract1.cs new file mode 100644 index 000000000..11766a045 --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract1.cs @@ -0,0 +1,28 @@ +namespace Neo.TestEngine.UnitTests.TestClasses +{ + public class Contract1 : SmartContract.Framework.SmartContract + { + public static byte[] unitTest_001() + { + var nb = new byte[] { 1, 2, 3, 4 }; + return nb; + } + + public static void testVoid() + { + var nb = new byte[] { 1, 2, 3, 4 }; + } + + public static byte[] testArgs1(byte a) + { + var nb = new byte[] { 1, 2, 3, 3 }; + nb[3] = a; + return nb; + } + + public static object testArgs2(byte[] a) + { + return a; + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/TestClasses/Contract2.cs b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract2.cs new file mode 100644 index 000000000..e5d8bfb0c --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract2.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel; + +namespace Neo.TestEngine.UnitTests.TestClasses +{ + public class Contract2 : SmartContract.Framework.SmartContract + { + [DisplayName("event")] + public static event Action notify; + + public static byte UnitTest_002(object arg1, object arg2) + { + notify(arg1); + notify(arg2); + var nb = new byte[] { 1, 2, 3, 4 }; + return nb[2]; + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_CheckWitness.cs b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_CheckWitness.cs new file mode 100644 index 000000000..49de6dea9 --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_CheckWitness.cs @@ -0,0 +1,12 @@ +using Neo.SmartContract.Framework.Services; + +namespace Neo.TestEngine.UnitTests.TestClasses +{ + public class Contract_CheckWitness : SmartContract.Framework.SmartContract + { + public static bool testWitness(UInt160 signature) + { + return Runtime.CheckWitness(signature); + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_ContractCall.cs b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_ContractCall.cs new file mode 100644 index 000000000..3ef0b44fb --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_ContractCall.cs @@ -0,0 +1,25 @@ +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Attributes; + +namespace Neo.TestEngine.UnitTests.TestClasses +{ + [Contract("0xfaf5e0e43f3fdeb58cdae3c1630a1db650b05403")] + public class Contract1 + { + public static extern byte[] testArgs1(byte a); + public static extern void testVoid(); + } + + public class Contract_ContractCall : SmartContract.Framework.SmartContract + { + public static byte[] testContractCall() + { + return Contract1.testArgs1((byte)4); + } + + public static void testContractCallVoid() + { + Contract1.testVoid(); + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_Time.cs b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_Time.cs new file mode 100644 index 000000000..db224e529 --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/TestClasses/Contract_Time.cs @@ -0,0 +1,18 @@ +using Neo.SmartContract.Framework.Native; +using Neo.SmartContract.Framework.Services; + +namespace Neo.SmartContract.Framework.UnitTests.TestClasses +{ + public class Contract_Runtime : SmartContract + { + public static ulong GetTime() + { + return Runtime.Time; + } + + public static object GetBlock(uint index) + { + return Ledger.GetBlock(index); + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/UnitTest_Block.cs b/tests/Neo.TestEngine.UnitTests/UnitTest_Block.cs new file mode 100644 index 000000000..3d1530a81 --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/UnitTest_Block.cs @@ -0,0 +1,145 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Json; +using Neo.SmartContract.Native; +using Neo.TestEngine.UnitTests.Utils; +using Neo.TestingEngine; +using Neo.VM; +using Neo.VM.Types; +using System.IO; + +namespace Neo.TestEngine.UnitTests +{ + [TestClass] + public class UnitTest_Block + { + [TestInitialize] + public void Init() + { + string path = Directory.GetCurrentDirectory(); + + CSharpCompiler.Compile(path + "/TestClasses/Contract1.cs"); + CSharpCompiler.Compile(path + "/TestClasses/Contract_Time.cs"); + Engine.Instance.Reset(); + } + + [TestMethod] + public void Test_Block_Height() + { + uint height = 16; + + var json = new JObject(); + json["path"] = "./TestClasses/Contract1.nef"; + json["method"] = "testVoid"; + json["height"] = height; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + Assert.AreEqual(height, Engine.Instance.Height); + } + + [TestMethod] + public void Test_Include_Block() + { + uint height = 10; + ulong timestamp = TestBlockchain.TheNeoSystem.GenesisBlock.Timestamp + 20 * TestBlockchain.TheNeoSystem.Settings.MillisecondsPerBlock; + + var blockJson = new JObject(); + blockJson["index"] = height; + blockJson["timestamp"] = timestamp; + blockJson["transactions"] = new JArray(); + + var json = new JObject(); + json["path"] = "./TestClasses/Contract_Time.nef"; + json["method"] = "getTime"; + json["blocks"] = new JArray() { blockJson }; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + Assert.AreEqual(height, Engine.Instance.Height); + + // test result + StackItem wantresult = timestamp; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + } + + [TestMethod] + public void Test_Include_Transaction() + { + var signer = new JObject(); + signer["account"] = "0x0000000000000000000000000000000000000000"; + signer["scopes"] = "None"; + + var transactionJson = new JObject(); + transactionJson["script"] = "EMAMB2dldFRpbWUMFIsccf/5cagRfaIDVBBMkYOwR666QWJ9W1I="; + transactionJson["witnesses"] = new JArray(); + transactionJson["signers"] = new JArray() + { + signer + }; + + uint height = 1; + ulong timestamp = TestBlockchain.TheNeoSystem.GenesisBlock.Timestamp + 20 * TestBlockchain.TheNeoSystem.Settings.MillisecondsPerBlock; + + var blockJson = new JObject(); + blockJson["index"] = height; + blockJson["timestamp"] = timestamp; + blockJson["transactions"] = new JArray() + { + transactionJson + }; + + var json = new JObject(); + json["path"] = "./TestClasses/Contract_Time.nef"; + json["method"] = "getBlock"; + json["arguments"] = new JArray() { ((StackItem)height).ToParameter().ToJson() }; + json["blocks"] = new JArray() { blockJson }; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + Assert.AreEqual(height, Engine.Instance.Height); + + // test result + var block = NativeContract.Ledger.GetBlock(Engine.Instance.Snapshot, Engine.Instance.Height); + Assert.IsNotNull(block); + Assert.AreEqual(block.Transactions.Length, ((JArray)blockJson["transactions"]).Count); + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/UnitTest_CheckWitness.cs b/tests/Neo.TestEngine.UnitTests/UnitTest_CheckWitness.cs new file mode 100644 index 000000000..2a56ee5d9 --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/UnitTest_CheckWitness.cs @@ -0,0 +1,103 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo; +using Neo.Json; +using Neo.SmartContract; +using Neo.TestEngine.UnitTests.Utils; +using Neo.TestingEngine; +using Neo.VM; +using Neo.VM.Types; +using Neo.Wallets; +using System.IO; + +namespace Neo.TestEngine.UnitTests +{ + [TestClass] + public class UnitTest_CheckWitness + { + [TestInitialize] + public void Init() + { + string path = Directory.GetCurrentDirectory(); + CSharpCompiler.Compile(path + "/TestClasses/Contract_CheckWitness.cs"); + Engine.Instance.Reset(); + } + + [TestMethod] + public void Test_Check_Witness() + { + var scripthash = "NiNmXL8FjEUEs1nfX9uHFBNaenxDHJtmuB".ToScriptHash(ProtocolSettings.Default.AddressVersion); + var param = new ContractParameter(ContractParameterType.Hash160) + { + Value = scripthash.ToString().Substring(2) + }; + + var json = new JObject(); + json["path"] = "./TestClasses/Contract_CheckWitness.nef"; + json["method"] = "testWitness"; + json["arguments"] = new JArray() { param.ToJson() }; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have an error + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // vm state must've faulted + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // result stack must be empty + StackItem wantresult = false; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + } + + [TestMethod] + public void Test_Check_Witness_With_Sign() + { + var scripthash = "NiNmXL8FjEUEs1nfX9uHFBNaenxDHJtmuB".ToScriptHash(ProtocolSettings.Default.AddressVersion); + var param = new ContractParameter(ContractParameterType.Hash160) + { + Value = scripthash.ToString().Substring(2) + }; + + var json = new JObject(); + json["path"] = "./TestClasses/Contract_CheckWitness.nef"; + json["method"] = "testWitness"; + json["arguments"] = new JArray() { param.ToJson() }; + + var signer = new JObject(); + signer["account"] = scripthash.ToString(); + json["signeraccounts"] = new JArray() { signer }; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have an error + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // vm state must've faulted + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // result stack must be empty + StackItem wantresult = true; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/UnitTest_ContractCall.cs b/tests/Neo.TestEngine.UnitTests/UnitTest_ContractCall.cs new file mode 100644 index 000000000..c711fc62a --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/UnitTest_ContractCall.cs @@ -0,0 +1,94 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Json; +using Neo.TestEngine.UnitTests.Utils; +using Neo.TestingEngine; +using Neo.VM; +using Neo.VM.Types; +using System.IO; + +namespace Neo.TestEngine.UnitTests +{ + [TestClass] + public class UnitTest_ContractCall + { + [TestInitialize] + public void Init() + { + string path = Directory.GetCurrentDirectory(); + CSharpCompiler.Compile(path + "/TestClasses/Contract_ContractCall.cs"); + Engine.Instance.Reset(); + } + + [TestMethod] + public void Test_Json() + { + var contract = new JObject(); + contract["nef"] = "./TestClasses/Contract1.nef"; + + var json = new JObject(); + json["path"] = "./TestClasses/Contract_ContractCall.nef"; + json["method"] = "testContractCall"; + json["contracts"] = new JArray() + { + contract + }; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // test result + StackItem wantresult = new byte[] { 1, 2, 3, 4 }; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + } + + [TestMethod] + public void Test_ContractCall_Void() + { + var contract = new JObject(); + contract["nef"] = "./TestClasses/Contract1.nef"; + + var json = new JObject(); + json["path"] = "./TestClasses/Contract_ContractCall.nef"; + json["method"] = "testContractCallVoid"; + json["contracts"] = new JArray() + { + contract + }; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // test result + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.AreEqual(0, resultStack.Count); + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/UnitTest_Invoke.cs b/tests/Neo.TestEngine.UnitTests/UnitTest_Invoke.cs new file mode 100644 index 000000000..419322f44 --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/UnitTest_Invoke.cs @@ -0,0 +1,315 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Json; +using Neo.TestEngine.UnitTests.Utils; +using Neo.TestingEngine; +using Neo.VM; +using Neo.VM.Types; +using System.IO; + +namespace Neo.TestEngine.UnitTests +{ + [TestClass] + public class UnitTest_Invoke + { + [TestInitialize] + public void Init() + { + string path = Directory.GetCurrentDirectory(); + CSharpCompiler.Compile(path + "/TestClasses/Contract1.cs"); + Engine.Instance.Reset(); + } + + [TestMethod] + public void Test_Missing_Arguments() + { + var args = new string[] { + "./TestClasses/Contract1.nef" + }; + var result = Program.Run(args); + + Assert.IsTrue(result.ContainsProperty("error")); + Assert.AreEqual( + "One or more arguments are missing\nExpected arguments: ", + result["error"].AsString() + ); + } + + [TestMethod] + public void Test_Method_Without_Parameters_Void() + { + var args = new string[] { + "./TestClasses/Contract1.nef", + "testVoid" + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // test result + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 0); + } + + [TestMethod] + public void Test_Method_Without_Parameters_With_Return() + { + var args = new string[] { + "./TestClasses/Contract1.nef", + "unitTest_001" + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // test result + StackItem wantresult = new byte[] { 1, 2, 3, 4 }; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + } + + [TestMethod] + public void Test_Method_With_Parameters() + { + StackItem arguments = 16; + var args = new string[] { + "./TestClasses/Contract1.nef", + "testArgs1", + arguments.ToParameter().ToJson().ToString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // test result + StackItem wantresult = new byte[] { 1, 2, 3, 16 }; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + } + + [TestMethod] + public void Test_Method_With_Misstyped_Parameters() + { + var args = new string[] { + "./TestClasses/Contract1.nef", + "testArgs1" + }; + var result = Program.Run(args); + + // mustn't have an error + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNotNull(result["error"]); + + // vm state must've faulted + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.FAULT.ToString()); + + // result stack must be empty + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 0); + } + + [TestMethod] + public void Test_Method_With_Parameters_Missing() + { + StackItem arguments = 16; + var jsonArgument = arguments.ToParameter().ToJson().ToString(); + var args = new string[] { + "./TestClasses/Contract1.nef", + "testArgs1", + $"{jsonArgument} {jsonArgument}" + }; + var result = Program.Run(args); + + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNotNull(result["error"]); + Assert.AreEqual(result.Properties.Count, 1); + } + + [TestMethod] + public void Test_File_Does_Not_Exist() + { + var args = new string[] { + "./TestClasses/Contract0.nef", + "testArgs1", + }; + var result = Program.Run(args); + + Assert.IsTrue(result.ContainsProperty("error")); + Assert.AreEqual(result["error"].AsString(), "File doesn't exists"); + } + + [TestMethod] + public void Test_Invalid_File() + { + var args = new string[] { + "./TestClasses/Contract1.cs", + "testArgs1", + }; + var result = Program.Run(args); + + Assert.IsTrue(result.ContainsProperty("error")); + Assert.AreEqual(result["error"].AsString(), "Invalid file. A .nef file required."); + } + + [TestMethod] + public void Test_Json_Missing_Fields() + { + var json = new JObject(); + json["path"] = "./TestClasses/Contract1.nef"; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + Assert.IsTrue(result.ContainsProperty("error")); + Assert.AreEqual("Missing field: 'method'", result["error"].AsString()); + } + + [TestMethod] + public void Test_Json() + { + var json = new JObject(); + json["path"] = "./TestClasses/Contract1.nef"; + json["method"] = "unitTest_001"; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // test result + StackItem wantresult = new byte[] { 1, 2, 3, 4 }; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + } + + [TestMethod] + public void Test_Json_With_Parameters() + { + StackItem arguments = 16; + + var json = new JObject(); + json["path"] = "./TestClasses/Contract1.nef"; + json["method"] = "testArgs1"; + json["arguments"] = new JArray() { arguments.ToParameter().ToJson() }; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // test result + StackItem wantresult = new byte[] { 1, 2, 3, 16 }; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + } + + [TestMethod] + public void Test_Json_With_Storage() + { + PrimitiveType key = "example"; + key = new ByteString(key.GetSpan().ToArray()); + var storageKey = new JObject(); + storageKey["id"] = 0; + storageKey["key"] = key.ToParameter().ToJson(); + + StackItem value = "123"; + value = new ByteString(value.GetSpan().ToArray()); + var storageValue = new JObject(); + storageValue["isconstant"] = false; + storageValue["value"] = value.ToParameter().ToJson(); + + var storageItem = new JObject(); + storageItem["key"] = storageKey; + storageItem["value"] = storageValue; + + StackItem arguments = 16; + var json = new JObject(); + json["path"] = "./TestClasses/Contract1.nef"; + json["method"] = "testArgs1"; + json["arguments"] = new JArray() { arguments.ToParameter().ToJson() }; + json["storage"] = new JArray() { storageItem }; + + var args = new string[] { + json.AsString() + }; + var result = Program.Run(args); + + // search in the storage + Assert.IsTrue(result.ContainsProperty("storage")); + Assert.IsInstanceOfType(result["storage"], typeof(JArray)); + + storageKey["key"] = key.ToJson(); + storageValue["value"] = value.ToJson(); + var storageArray = result["storage"] as JArray; + + var contains = false; + foreach (var pair in storageArray) + { + if (pair.AsString() == storageItem.AsString()) + { + contains = true; + break; + } + } + Assert.IsTrue(contains); + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/UnitTest_Notification.cs b/tests/Neo.TestEngine.UnitTests/UnitTest_Notification.cs new file mode 100644 index 000000000..64dcf9b12 --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/UnitTest_Notification.cs @@ -0,0 +1,72 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Json; +using Neo.TestEngine.UnitTests.Utils; +using Neo.TestingEngine; +using Neo.VM; +using Neo.VM.Types; +using System.IO; + +namespace Neo.TestEngine.UnitTests +{ + [TestClass] + public class UnitTest_Notification + { + [TestInitialize] + public void Init() + { + string path = Directory.GetCurrentDirectory(); + CSharpCompiler.Compile(path + "/TestClasses/Contract2.cs"); + Engine.Instance.Reset(); + } + + [TestMethod] + public void Test_Notification() + { + StackItem arg1 = 16; + StackItem arg2 = "Teste"; + + var args = new string[] { + "./TestClasses/Contract2.nef", + "unitTest_002", + arg1.ToParameter().ToJson().ToString(), + arg2.ToParameter().ToJson().ToString() + }; + var result = Program.Run(args); + + // mustn't have errors + Assert.IsTrue(result.ContainsProperty("error")); + Assert.IsNull(result["error"]); + + // test state + Assert.IsTrue(result.ContainsProperty("vmstate")); + Assert.AreEqual(result["vmstate"].AsString(), VMState.HALT.ToString()); + + // test result + StackItem wantresult = 3; + Assert.IsTrue(result.ContainsProperty("resultstack")); + Assert.IsInstanceOfType(result["resultstack"], typeof(JArray)); + + var resultStack = result["resultstack"] as JArray; + Assert.IsTrue(resultStack.Count == 1); + Assert.AreEqual(resultStack[0]["value"].AsString(), wantresult.ToJson()["value"].AsString()); + + // test notifications + Assert.IsTrue(result.ContainsProperty("notifications")); + Assert.IsInstanceOfType(result["notifications"], typeof(JArray)); + + var notifications = result["notifications"] as JArray; + Assert.IsTrue(notifications.Count == 3); + + // emitted Deploy notification when the contract was deployed + Assert.AreEqual(notifications[0]["eventname"].AsString(), "Deploy"); + + Assert.AreEqual(notifications[1]["eventname"].AsString(), "event"); + var firstNotifications = notifications[1]["value"]; + Assert.AreEqual((firstNotifications["value"] as JArray)[0].AsString(), arg1.ToJson().ToString()); + + Assert.AreEqual(notifications[2]["eventname"].AsString(), "event"); + var secondNotifications = notifications[2]["value"]; + Assert.AreEqual((secondNotifications["value"] as JArray)[0].AsString(), arg2.ToJson().ToString()); + } + } +} diff --git a/tests/Neo.TestEngine.UnitTests/Utils/CSharpCompiler.cs b/tests/Neo.TestEngine.UnitTests/Utils/CSharpCompiler.cs new file mode 100644 index 000000000..fd6bdb101 --- /dev/null +++ b/tests/Neo.TestEngine.UnitTests/Utils/CSharpCompiler.cs @@ -0,0 +1,28 @@ +using Neo.Compiler; +using Neo.IO; +using System; +using System.IO; + +namespace Neo.TestEngine.UnitTests.Utils +{ + public class CSharpCompiler + { + public static void Compile(string filepath) + { + CompilationContext context = CompilationContext.CompileSources(new[] { filepath }, new Options + { + AddressVersion = ProtocolSettings.Default.AddressVersion + }); + + if (!context.Success) + { + throw new Exception(string.Format("Could not compile '{0}'", filepath)); + } + var nefPath = filepath.Replace(".cs", ".nef"); + File.WriteAllBytes(nefPath, context.CreateExecutable().ToArray()); + + var manifestPath = filepath.Replace(".cs", ".manifest.json"); + File.WriteAllBytes(manifestPath, context.CreateManifest().ToByteArray(false)); + } + } +}