diff --git a/neo-devpack-dotnet.sln b/neo-devpack-dotnet.sln index ca3e29413..bee1e22f5 100644 --- a/neo-devpack-dotnet.sln +++ b/neo-devpack-dotnet.sln @@ -31,7 +31,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Template.NEP5.UnitTests", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestEngine", "src\TestEngine\TestEngine.csproj", "{39347E90-FC12-4018-8F22-828F0F1B2F7D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestEngine.UnitTests", "tests\TestEngine.UnitTests\TestEngine.UnitTests.csproj", "{FFDC189F-2F72-4542-B28B-8EFA0F28E3CF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestEngine.UnitTests", "tests\TestEngine.UnitTests\TestEngine.UnitTests.csproj", "{FFDC189F-2F72-4542-B28B-8EFA0F28E3CF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/TestEngine/Engine.cs b/src/TestEngine/Engine.cs index 7bf17074d..a4b5e2e45 100644 --- a/src/TestEngine/Engine.cs +++ b/src/TestEngine/Engine.cs @@ -6,6 +6,7 @@ using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; +using System; using System.Collections.Generic; namespace Neo.TestingEngine @@ -33,6 +34,8 @@ private Engine() Reset(); } + public int BlockCount => ((TestDataCache)engine.Snapshot.Blocks).Count(); + public void Reset() { engine = SetupNativeContracts(); @@ -63,6 +66,34 @@ public void AddSmartContract(string path) } } + public void IncreaseBlockCount(uint newHeight) + { + var snapshot = (TestSnapshot)engine.Snapshot; + var blocks = (TestDataCache)snapshot.Blocks; + Block newBlock; + Block lastBlock = null; + if (blocks.Count() == 0) + { + newBlock = Blockchain.GenesisBlock; + } + else + { + newBlock = CreateBlock(); + } + + while (blocks.Count() < newHeight) + { + var hash = newBlock.Hash; + var trim = newBlock.Trim(); + blocks.AddForTest(hash, trim); + lastBlock = newBlock; + newBlock = CreateBlock(); + } + + var index = (uint)(blocks.Count() - 1); + snapshot.SetCurrentBlockHash(index, lastBlock.Hash); + } + public void SetStorage(Dictionary storage) { foreach (var data in storage) @@ -87,21 +118,8 @@ public JObject Run(string method, StackItem[] args) private TestEngine SetupNativeContracts() { - var block = new Block() - { - Index = 0, - ConsensusData = new ConsensusData(), - Transactions = new Transaction[0], - Witness = new Witness() - { - InvocationScript = new byte[0], - VerificationScript = Contract.CreateSignatureRedeemScript(ECPoint.FromBytes(PubKey, ECCurve.Secp256k1)) - }, - NextConsensus = UInt160.Zero, - MerkleRoot = UInt256.Zero, - PrevHash = UInt256.Zero - }; - + SetConsensus(); + var block = Blockchain.GenesisBlock; TestEngine engine = new TestEngine(TriggerType.Application, block); ((TestSnapshot)engine.Snapshot).SetPersistingBlock(block); @@ -117,6 +135,42 @@ private TestEngine SetupNativeContracts() return engine; } + private void SetConsensus() + { + var _ = TestBlockchain.TheNeoSystem; + var store = Blockchain.Singleton.Store; + var block = Blockchain.GenesisBlock; + } + + private Block CreateBlock() + { + var blocks = engine.Snapshot.Blocks.Seek().GetEnumerator(); + while (blocks.MoveNext()) + { } + + var (blockHash, trimmedBlock) = blocks.Current; + if (blockHash == null) + { + (blockHash, trimmedBlock) = (Blockchain.GenesisBlock.Hash, Blockchain.GenesisBlock.Trim()); + } + + return new Block() + { + Index = trimmedBlock.Index + 1, + Timestamp = trimmedBlock.Timestamp + Blockchain.MillisecondsPerBlock, + ConsensusData = new ConsensusData(), + Transactions = new Transaction[0], + Witness = new Witness() + { + InvocationScript = new byte[0], + VerificationScript = Contract.CreateSignatureRedeemScript(ECPoint.FromBytes(PubKey, ECCurve.Secp256k1)) + }, + NextConsensus = trimmedBlock.NextConsensus, + MerkleRoot = trimmedBlock.MerkleRoot, + PrevHash = blockHash + }; + } + private static byte[] HexString2Bytes(string str) { if (str.IndexOf("0x") == 0) diff --git a/src/TestEngine/Program.cs b/src/TestEngine/Program.cs index 3c05bad1e..453fcca34 100644 --- a/src/TestEngine/Program.cs +++ b/src/TestEngine/Program.cs @@ -138,6 +138,11 @@ public static JObject RunWithJson(JObject json) { smartContractTestCase.contracts = GetContractsFromJson(json["contracts"]); } + + if (json.ContainsProperty("height")) + { + smartContractTestCase.currentHeight = uint.Parse(json["height"].AsString()); + } return Run(smartContractTestCase); } catch (Exception e) @@ -149,9 +154,7 @@ public static JObject RunWithJson(JObject json) /// /// Runs the given method from a nef script /// - /// Absolute path of the script - /// The name of the targeted method - /// Arguments of the method + /// 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) { @@ -165,11 +168,17 @@ public static JObject Run(SmartContractTest smartContractTest) { Engine.Instance.SetStorage(smartContractTest.storage); } + foreach (var contract in smartContractTest.contracts) { Engine.Instance.AddSmartContract(contract.nefPath); } + if (smartContractTest.currentHeight > 0) + { + Engine.Instance.IncreaseBlockCount(smartContractTest.currentHeight); + } + var stackParams = GetStackItemParameters(smartContractTest.methodParameters); return Engine.Instance.Run(smartContractTest.methodName, stackParams); } diff --git a/src/TestEngine/SmartContractTest.cs b/src/TestEngine/SmartContractTest.cs index 61eb20291..a89fdda9b 100644 --- a/src/TestEngine/SmartContractTest.cs +++ b/src/TestEngine/SmartContractTest.cs @@ -11,6 +11,7 @@ public class SmartContractTest public JArray methodParameters; public Dictionary storage = new Dictionary(); public List contracts = new List(); + public uint currentHeight = 0; public SmartContractTest(string path, string method, JArray parameters) { diff --git a/src/TestEngine/TestUtils/TestAccount.cs b/src/TestEngine/TestUtils/TestAccount.cs new file mode 100644 index 000000000..c9fdda8f7 --- /dev/null +++ b/src/TestEngine/TestUtils/TestAccount.cs @@ -0,0 +1,33 @@ +using Neo.Wallets; + +namespace Neo.TestingEngine +{ + class TestAccount : WalletAccount + { + public override bool HasKey => this.key != null; + + private byte[] privateKey; + private UInt160 scriptHash; + private KeyPair key = null; + + public TestAccount(UInt160 scriptHash, byte[] privKey = null) : base(scriptHash) + { + if (privKey != null) + { + this.privateKey = privKey; + } + else + { + this.privateKey = new byte[32]; + } + + this.scriptHash = scriptHash; + this.key = new KeyPair(this.privateKey); + } + + public override KeyPair GetKey() + { + return this.key; + } + } +} diff --git a/tests/Neo.SmartContract.Framework.UnitTests/TestBlockchain.cs b/src/TestEngine/TestUtils/TestBlockchain.cs similarity index 86% rename from tests/Neo.SmartContract.Framework.UnitTests/TestBlockchain.cs rename to src/TestEngine/TestUtils/TestBlockchain.cs index 1a38d0089..5fa018354 100644 --- a/tests/Neo.SmartContract.Framework.UnitTests/TestBlockchain.cs +++ b/src/TestEngine/TestUtils/TestBlockchain.cs @@ -1,6 +1,6 @@ using Neo.Ledger; -namespace Neo.SmartContract.Framework.UnitTests +namespace Neo.TestingEngine { public static class TestBlockchain { diff --git a/src/TestEngine/TestUtils/TestConsensusContext.cs b/src/TestEngine/TestUtils/TestConsensusContext.cs new file mode 100644 index 000000000..d69c7fb11 --- /dev/null +++ b/src/TestEngine/TestUtils/TestConsensusContext.cs @@ -0,0 +1,40 @@ +using Neo.Consensus; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.Wallets; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.TestingEngine +{ + public class TestConsensusContext : ConsensusContext + { + public TestConsensusContext(ECPoint[] validators, Wallet wallet, IStore store) : base(wallet, store) + { + this.Validators = validators; + this.CommitPayloads = new ConsensusPayload[validators.Length]; + } + + public void SetBlock(Block block) + { + this.Block = block; + } + + public Block CreateBlock() + { + EnsureHeader(); + Contract contract = Contract.CreateMultiSigContract(M, Validators); + ContractParametersContext sc = new ContractParametersContext(Block); + + var witness = new Witness(); + witness.InvocationScript = contract.Script; + witness.VerificationScript = new byte[0]; + Block.Witness = witness; + Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); + return Block; + } + + } +} diff --git a/src/TestEngine/TestUtils/TestDataCache.cs b/src/TestEngine/TestUtils/TestDataCache.cs index c561ed17f..68d7f12b9 100644 --- a/src/TestEngine/TestUtils/TestDataCache.cs +++ b/src/TestEngine/TestUtils/TestDataCache.cs @@ -63,6 +63,14 @@ public void Clear() dic.Clear(); } + /// + /// Gets the size of the storage for unit test + /// + public int Count() + { + return dic.Count; + } + /// /// Include a new value to the storage for unit test /// diff --git a/src/TestEngine/TestUtils/TestMetaDataCache.cs b/src/TestEngine/TestUtils/TestMetaDataCache.cs index 6523d54a3..cf5763081 100644 --- a/src/TestEngine/TestUtils/TestMetaDataCache.cs +++ b/src/TestEngine/TestUtils/TestMetaDataCache.cs @@ -5,6 +5,7 @@ namespace Neo.TestingEngine { public class TestMetaDataCache : MetaDataCache where T : class, ICloneable, ISerializable, new() { + private T metadata = null; public TestMetaDataCache() : base(null) { @@ -16,11 +17,17 @@ protected override void AddInternal(T item) protected override T TryGetInternal() { - return null; + return metadata; } protected override void UpdateInternal(T item) { + metadata = item; + } + + public void Update(T item) + { + UpdateInternal(item); } } } diff --git a/src/TestEngine/TestUtils/TestSnapshot.cs b/src/TestEngine/TestUtils/TestSnapshot.cs index db339dfa4..029fedd94 100644 --- a/src/TestEngine/TestUtils/TestSnapshot.cs +++ b/src/TestEngine/TestUtils/TestSnapshot.cs @@ -49,5 +49,25 @@ public void ClearStorage() { ((TestDataCache)this._Storages).Clear(); } + + public void SetCurrentBlockHash(uint index, UInt256 hash) + { + if (hash != null && Blocks is TestDataCache blocks) + { + var blocksCount = blocks.Count(); + + if (index > blocksCount) + { + index = (uint)blocksCount; + } + + var hashIndex = new HashIndexState() + { + Hash = hash, + Index = index + }; + ((TestMetaDataCache)BlockHashIndex).Update(hashIndex); + } + } } } diff --git a/src/TestEngine/TestUtils/TestWallet.cs b/src/TestEngine/TestUtils/TestWallet.cs new file mode 100644 index 000000000..580b7016e --- /dev/null +++ b/src/TestEngine/TestUtils/TestWallet.cs @@ -0,0 +1,76 @@ +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 TestWallet(UInt160 scriptHash) : base("", "TestWallet") + { + this.accounts = new Dictionary() + { + { scriptHash, new TestAccount(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) + { + throw new NotImplementedException(); + } + + 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; + } + + 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; + } + } +} diff --git a/tests/TestEngine.UnitTests/UnitTest_BlockHeight.cs b/tests/TestEngine.UnitTests/UnitTest_BlockHeight.cs new file mode 100644 index 000000000..fd57b525a --- /dev/null +++ b/tests/TestEngine.UnitTests/UnitTest_BlockHeight.cs @@ -0,0 +1,55 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Json; +using Neo.TestingEngine; +using Neo.VM; +using Neo.VM.Types; +using System.IO; +using Compiler = Neo.Compiler.Program; + +namespace TestEngine.UnitTests +{ + [TestClass] + public class UnitTest_BlockHeight + { + [TestInitialize] + public void Init() + { + string path = Directory.GetCurrentDirectory(); + var option = new Compiler.Options() + { + File = path + "/TestClasses/Contract1.cs" + }; + Compiler.Compile(option); + + //Compile changes the path, reseting so that other UT won't break + Directory.SetCurrentDirectory(path); + Engine.Instance.Reset(); + } + + [TestMethod] + public void Test_Json() + { + var 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("vm_state")); + Assert.AreEqual(result["vm_state"].AsString(), VMState.HALT.ToString()); + + Assert.AreEqual(height, Engine.Instance.BlockCount); + } + } +}