Skip to content

Commit 447ee42

Browse files
authored
TestEngine: Migrate current Framework's ut (#968)
* Migrate current ut's * Restore tests not related to a contract * revert namespace change * All artifacts * revert address * Migrate some UT * format * Migrate 3 ut * migrate 2 ut * fix namespace * Remove SyscallTest * Migrate 2 ut more * Helper ut * Two uts more * Workflow * Two ut more * Oracle * clean * Rename contract and StdLib * Runtime * One ut more * Native ut * UT fail, how to send an interopInterface? :S * Ut pointers * Fix ut * Clean and extend * Contract UT * TODO BlockchainTest * Update csproj * Fix conflicts * clean * Start with blockchain its * Revert using short * TODO Crypto * Blockchain UT * secp256k1 * Increase coverage * Fix merge * fix using sort * Add Uint coverage * Fix CurrentBlock * previous from storage * Update src/Neo.SmartContract.Testing/Native/LedgerContract.cs * Persisting block * clean * Clean * Clean
1 parent 92e1372 commit 447ee42

File tree

86 files changed

+3326
-2621
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+3326
-2621
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ jobs:
5959
run: |
6060
dotnet test ./tests/Neo.SmartContract.Framework.UnitTests \
6161
--no-build \
62-
-l "console;verbosity=normal" \
62+
-e "COVERAGE_MERGE_JOIN=${GITHUB_WORKSPACE}/coverage-join/coverage.json" \
63+
-l "console;verbosity=detailed" \
6364
-p:CollectCoverage=true \
6465
-p:CoverletOutput=${GITHUB_WORKSPACE}/coverage-join/ \
6566
-p:MergeWith=${GITHUB_WORKSPACE}/coverage-join/coverage.json \

src/Neo.SmartContract.Testing/Native/LedgerContract.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
using Neo.IO;
12
using Neo.Network.P2P.Payloads;
2-
using Neo.SmartContract.Native;
33
using Neo.VM;
44
using System.ComponentModel;
55
using System.Numerics;
@@ -30,23 +30,51 @@ public abstract class LedgerContract : SmartContract
3030

3131
#region Safe methods
3232

33+
#region Helpers
34+
35+
/// <summary>
36+
/// Safe helper method
37+
/// </summary>
38+
public Models.Block? GetBlock(UInt256 hash)
39+
=> GetBlock(hash.ToArray());
40+
41+
/// <summary>
42+
/// Safe helper method
43+
/// </summary>
44+
public Models.Block? GetBlock(uint index)
45+
=> GetBlock(new BigInteger(index).ToByteArray());
46+
47+
/// <summary>
48+
/// Safe helper method
49+
/// </summary>
50+
public Models.Transaction? GetTransactionFromBlock(uint blockIndex, uint txIndex)
51+
=> GetTransactionFromBlock(new BigInteger(blockIndex).ToByteArray(), txIndex);
52+
53+
/// <summary>
54+
/// Safe helper method
55+
/// </summary>
56+
public Models.Transaction? GetTransactionFromBlock(UInt256 blockHash, uint txIndex)
57+
=> GetTransactionFromBlock(blockHash.ToArray(), txIndex);
58+
59+
#endregion
60+
3361
/// <summary>
3462
/// Safe method
3563
/// </summary>
3664
[DisplayName("getBlock")]
37-
public abstract TrimmedBlock? GetBlock(byte[]? indexOrHash);
65+
public abstract Models.Block? GetBlock(byte[]? indexOrHash);
3866

3967
/// <summary>
4068
/// Safe method
4169
/// </summary>
4270
[DisplayName("getTransaction")]
43-
public abstract Transaction? GetTransaction(UInt256? hash);
71+
public abstract Models.Transaction? GetTransaction(UInt256? hash);
4472

4573
/// <summary>
4674
/// Safe method
4775
/// </summary>
4876
[DisplayName("getTransactionFromBlock")]
49-
public abstract Transaction? GetTransactionFromBlock(byte[]? blockIndexOrHash, BigInteger? txIndex);
77+
public abstract Models.Transaction? GetTransactionFromBlock(byte[]? blockIndexOrHash, BigInteger? txIndex);
5078

5179
/// <summary>
5280
/// Safe method
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Neo.SmartContract.Testing.Attributes;
2+
3+
namespace Neo.SmartContract.Testing.Native.Models
4+
{
5+
public class Block
6+
{
7+
[FieldOrder(0)]
8+
public UInt256 Hash { get; set; }
9+
10+
[FieldOrder(1)]
11+
public uint Version { get; set; }
12+
13+
[FieldOrder(2)]
14+
public UInt256 PrevHash { get; set; }
15+
16+
[FieldOrder(3)]
17+
public UInt256 MerkleRoot { get; set; }
18+
19+
[FieldOrder(4)]
20+
public ulong Timestamp { get; set; }
21+
22+
[FieldOrder(5)]
23+
public ulong Nonce { get; set; }
24+
25+
[FieldOrder(6)]
26+
public uint Index { get; set; }
27+
28+
[FieldOrder(7)]
29+
public byte PrimaryIndex { get; set; }
30+
31+
[FieldOrder(8)]
32+
public UInt160 NextConsensus { get; set; }
33+
34+
[FieldOrder(9)]
35+
public int TransactionsCount { get; set; }
36+
}
37+
}

src/Neo.SmartContract.Testing/Native/NeoToken.Models.cs renamed to src/Neo.SmartContract.Testing/Native/Models/Candidate.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
using Neo.SmartContract.Testing.Attributes;
33
using System.Numerics;
44

5-
namespace Neo.SmartContract.Testing.Native;
6-
7-
public abstract partial class NeoToken : SmartContract
5+
namespace Neo.SmartContract.Testing.Native.Models
86
{
97
public class Candidate
108
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Neo.SmartContract.Testing.Attributes;
2+
3+
namespace Neo.SmartContract.Testing.Native.Models
4+
{
5+
public class Transaction
6+
{
7+
[FieldOrder(0)]
8+
public UInt256? Hash { get; set; }
9+
10+
[FieldOrder(1)]
11+
public byte Version { get; set; }
12+
13+
[FieldOrder(2)]
14+
public uint Nonce { get; set; }
15+
16+
[FieldOrder(3)]
17+
public UInt160? Sender { get; set; }
18+
19+
[FieldOrder(4)]
20+
public long SystemFee { get; set; }
21+
22+
[FieldOrder(5)]
23+
public long NetworkFee { get; set; }
24+
25+
[FieldOrder(6)]
26+
public uint ValidUntilBlock { get; set; }
27+
28+
[FieldOrder(7)]
29+
public byte[]? Script { get; set; }
30+
}
31+
}

src/Neo.SmartContract.Testing/Native/NeoToken.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public abstract partial class NeoToken : SmartContract, TestingStandards.INep17S
5151
/// <summary>
5252
/// Safe property
5353
/// </summary>
54-
public abstract Candidate[]? Candidates { [DisplayName("getCandidates")] get; }
54+
public abstract Models.Candidate[] Candidates { [DisplayName("getCandidates")] get; }
5555

5656
/// <summary>
5757
/// Safe property

src/Neo.SmartContract.Testing/NativeArtifacts.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Neo.Network.P2P.Payloads;
12
using Neo.Persistence;
23
using Neo.SmartContract.Testing.Native;
34
using System;
@@ -80,7 +81,8 @@ public NativeArtifacts(TestEngine engine)
8081
/// Initialize native contracts
8182
/// </summary>
8283
/// <param name="commit">Initialize native contracts</param>
83-
public void Initialize(bool commit = false)
84+
/// <returns>Genesis block</returns>
85+
public Block Initialize(bool commit = false)
8486
{
8587
_engine.Transaction.Script = Array.Empty<byte>(); // Store the script in the current transaction
8688

@@ -122,7 +124,7 @@ public void Initialize(bool commit = false)
122124

123125
method = native.GetType().GetMethod("PostPersist", BindingFlags.NonPublic | BindingFlags.Instance);
124126

125-
using (var engine = new TestingApplicationEngine(_engine, TriggerType.OnPersist, genesis, clonedSnapshot, genesis))
127+
using (var engine = new TestingApplicationEngine(_engine, TriggerType.PostPersist, genesis, clonedSnapshot, genesis))
126128
{
127129
engine.LoadScript(Array.Empty<byte>());
128130
if (method!.Invoke(native, new object[] { engine }) is not ContractTask task)
@@ -145,6 +147,8 @@ public void Initialize(bool commit = false)
145147

146148
ApplicationEngine.Log -= _engine.ApplicationEngineLog;
147149
ApplicationEngine.Notify -= _engine.ApplicationEngineNotify;
150+
151+
return genesis;
148152
}
149153
}
150154
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
using Neo.Cryptography;
2+
using Neo.Network.P2P.Payloads;
3+
using Neo.Persistence;
4+
using Neo.SmartContract.Native;
5+
using Neo.VM;
6+
using System;
7+
using System.Linq;
8+
using System.Reflection;
9+
10+
namespace Neo.SmartContract.Testing
11+
{
12+
public class PersistingBlock
13+
{
14+
private readonly TestEngine _engine;
15+
16+
/// <summary>
17+
/// Underlying block
18+
/// </summary>
19+
internal readonly Block UnderlyingBlock;
20+
21+
/// <summary>
22+
/// Index
23+
/// </summary>
24+
public uint Index => UnderlyingBlock.Header.Index;
25+
26+
/// <summary>
27+
/// Nonce
28+
/// </summary>
29+
public ulong Nonce
30+
{
31+
get => UnderlyingBlock.Header.Nonce;
32+
set => UnderlyingBlock.Header.Nonce = value;
33+
}
34+
35+
/// <summary>
36+
/// Time
37+
/// </summary>
38+
public TimeSpan Timestamp => TimeSpan.FromMilliseconds(UnderlyingBlock.Header.Timestamp);
39+
40+
/// <summary>
41+
/// Constructor
42+
/// </summary>
43+
/// <param name="engine">Engine</param>
44+
/// <param name="currentBlock">Current block</param>
45+
public PersistingBlock(TestEngine engine, Block currentBlock)
46+
{
47+
_engine = engine;
48+
UnderlyingBlock = new Block()
49+
{
50+
Header = CreateNextHeader(currentBlock.Header, TimeSpan.FromSeconds(15), currentBlock.Nonce),
51+
Transactions = Array.Empty<Transaction>(),
52+
};
53+
}
54+
55+
/// <summary>
56+
/// Advance
57+
/// </summary>
58+
/// <param name="elapsed">Elapsed time</param>
59+
public void Advance(TimeSpan elapsed)
60+
{
61+
UnderlyingBlock.Header.Timestamp += (ulong)elapsed.TotalMilliseconds;
62+
}
63+
64+
/// <summary>
65+
/// Skip blocks
66+
/// </summary>
67+
/// <param name="count">Count</param>
68+
/// <param name="elapsed">Elapsed</param>
69+
public void Skip(uint count, TimeSpan elapsed)
70+
{
71+
UnderlyingBlock.Header.Index += count;
72+
UnderlyingBlock.Header.Timestamp += (ulong)elapsed.TotalMilliseconds;
73+
}
74+
75+
/// <summary>
76+
/// Persist block
77+
/// </summary>
78+
/// <param name="tx">Transaction</param>
79+
/// <param name="state">State</param>
80+
/// <returns>Persisted block</returns>
81+
public Block Persist(Transaction tx, VMState state = VMState.HALT)
82+
{
83+
return Persist(new[] { tx }, new[] { state });
84+
}
85+
86+
/// <summary>
87+
/// Persist block
88+
/// </summary>
89+
/// <param name="txs">Transactions</param>
90+
/// <param name="states">States</param>
91+
/// <returns>Persisted block</returns>
92+
public Block Persist(Transaction[] txs, VMState[] states)
93+
{
94+
if (txs.Length != states.Length)
95+
{
96+
throw new ArgumentException("Transactions count and states count are different");
97+
}
98+
99+
// Build block
100+
101+
Block persist = new()
102+
{
103+
Header = UnderlyingBlock.Header,
104+
Transactions = txs,
105+
};
106+
persist.Header.MerkleRoot = MerkleTree.ComputeRoot(txs.Select(p => p.Hash).ToArray());
107+
108+
// Invoke Ledger.OnPersist
109+
110+
var native = NativeContract.Ledger;
111+
var method = native.GetType().GetMethod("OnPersist", BindingFlags.NonPublic | BindingFlags.Instance);
112+
113+
DataCache clonedSnapshot = _engine.Storage.Snapshot.CreateSnapshot();
114+
115+
using (var engine = new TestingApplicationEngine(_engine, TriggerType.OnPersist, persist, clonedSnapshot, persist))
116+
{
117+
engine.LoadScript(Array.Empty<byte>());
118+
if (method!.Invoke(native, new object[] { engine }) is not ContractTask task)
119+
throw new Exception($"Error casting {native.Name}.OnPersist to ContractTask");
120+
121+
task.GetAwaiter().GetResult();
122+
123+
if (engine.Execute() != VMState.HALT)
124+
throw new Exception($"Error executing {native.Name}.OnPersist");
125+
}
126+
127+
// Invoke Ledger.PostPersist
128+
129+
method = native.GetType().GetMethod("PostPersist", BindingFlags.NonPublic | BindingFlags.Instance);
130+
131+
using (var engine = new TestingApplicationEngine(_engine, TriggerType.PostPersist, persist, clonedSnapshot, persist))
132+
{
133+
engine.LoadScript(Array.Empty<byte>());
134+
if (method!.Invoke(native, new object[] { engine }) is not ContractTask task)
135+
throw new Exception($"Error casting {native.Name}.PostPersist to ContractTask");
136+
137+
task.GetAwaiter().GetResult();
138+
if (engine.Execute() != VMState.HALT)
139+
throw new Exception($"Error executing {native.Name}.PostPersist");
140+
}
141+
142+
// Update states
143+
144+
const byte prefix_Transaction = 11;
145+
146+
for (int x = 0; x < txs.Length; x++)
147+
{
148+
var transactionState = clonedSnapshot.TryGet(new KeyBuilder(_engine.Native.Ledger.Storage.Id, prefix_Transaction).Add(txs[x].Hash));
149+
transactionState.GetInteroperable<TransactionState>().State = states[x];
150+
}
151+
152+
// Commit changes and return block
153+
154+
UnderlyingBlock.Header = CreateNextHeader(persist.Header, TimeSpan.FromSeconds(15), persist.Nonce);
155+
clonedSnapshot.Commit();
156+
157+
return persist;
158+
}
159+
160+
/// <summary>
161+
/// Create next header
162+
/// </summary>
163+
/// <param name="previous">Previous</param>
164+
/// <param name="elapsed">Elapsed</param>
165+
/// <param name="nonce">Nonce</param>
166+
/// <returns>Header</returns>
167+
private static Header CreateNextHeader(Header previous, TimeSpan elapsed, ulong nonce = 0)
168+
{
169+
return new Header()
170+
{
171+
Version = previous.Version,
172+
Index = previous.Index + 1,
173+
MerkleRoot = UInt256.Zero,
174+
NextConsensus = previous.NextConsensus,
175+
Nonce = nonce,
176+
PrevHash = previous.Hash,
177+
PrimaryIndex = previous.PrimaryIndex,
178+
Timestamp = previous.Timestamp + (ulong)elapsed.TotalMilliseconds,
179+
Witness = new Witness()
180+
{
181+
InvocationScript = Array.Empty<byte>(),
182+
VerificationScript = Array.Empty<byte>(),
183+
}
184+
};
185+
}
186+
}
187+
}

src/Neo.SmartContract.Testing/README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,11 @@ The publicly exposed read-only properties are as follows:
7575
- **ValidatorsAddress**: Defines the address for the validators of the defined *ProtocolSettings*.
7676
- **CommitteeAddress**: Returns the address of the current chain's committee.
7777
- **Transaction**: Defines the transaction that will be used as `ScriptContainer` for the neo virtual machine, by default it updates the script of the same as calls are composed and executed, and the `Signers` will be used as validators for the `CheckWitness`, regardless of whether the signature is correct or not, so if you want to test with different wallets or scopes, you do not need to sign the transaction correctly, just set the desired signers.
78-
- **CurrentBlock**: Defaults to `Genesis` for the defined `ProtocolSettings`, but the height has been incremented by 1 to avoid issues related to the generation of gas from native contracts.
78+
- **PersistingBlock**: The block that will be persisted.
79+
- **Storage**: Abstracts access to storage, allowing for easy `Snapshots` as well as reverting them. Allows access to the storage of contracts, as well as manually altering their state. It's worth noting that a storage class is provided, which allows for reading the storage from an RPC endpoint. The class in question is named `RpcStore` and is available in the namespace `Neo.SmartContract.Testing.Storage.Rpc`.
7980

8081
And for read and write, we have:
8182

82-
- **Storage**: Abstracts access to storage, allowing for easy `Snapshots` as well as reverting them. Allows access to the storage of contracts, as well as manually altering their state. It's worth noting that a storage class is provided, which allows for reading the storage from an RPC endpoint. The class in question is named `RpcStore` and is available in the namespace `Neo.SmartContract.Testing.Storage.Rpc`.
83-
8483
- **Gas**: Sets the gas execution limit for contract calls. Sets the `NetworkFee` of the `Transaction` object.
8584
- **EnableCoverageCapture**: Enables or disables the coverage capture.
8685
- **Trigger**: The trigger of the execution.

0 commit comments

Comments
 (0)