Skip to content

Commit d64024b

Browse files
authored
TestEngine changes (#946)
* Update core * fix issue * Apply suggestions from code review Co-authored-by: Jimmy <[email protected]> * Add expected hash to OnXScriptHash * Add storage methods * clean change * Update neo --------- Co-authored-by: Jimmy <[email protected]>
1 parent 53a6418 commit d64024b

File tree

12 files changed

+142
-42
lines changed

12 files changed

+142
-42
lines changed

src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt160.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@ namespace Neo.SmartContract.Testing.InvalidTypes
22
{
33
public class InvalidUInt160
44
{
5+
/// <summary>
6+
/// Zero
7+
/// </summary>
8+
public static readonly UInt160 Zero = UInt160.Zero;
9+
510
/// <summary>
611
/// Null UInt160
712
/// </summary>
813
public static readonly UInt160? Null = null;
914

1015
/// <summary>
11-
/// This will be an invalid UInt160
16+
/// This will be an invalid UInt160 (ByteString)
17+
/// </summary>
18+
public static readonly UInt160 InvalidLength = new();
19+
20+
/// <summary>
21+
/// This will be an invalid UInt160 (Integer)
1222
/// </summary>
13-
public static readonly UInt160 Invalid = new();
23+
public static readonly UInt160 InvalidType = new();
1424
}
1525
}

src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt256.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@ namespace Neo.SmartContract.Testing.InvalidTypes
22
{
33
public class InvalidUInt256
44
{
5+
/// <summary>
6+
/// Zero
7+
/// </summary>
8+
public static readonly UInt160 Zero = UInt160.Zero;
9+
510
/// <summary>
611
/// Null UInt256
712
/// </summary>
813
public static readonly UInt256? Null = null;
914

1015
/// <summary>
11-
/// This will be an invalid UInt256
16+
/// This will be an invalid UInt256 (ByteString)
17+
/// </summary>
18+
public static readonly UInt256 InvalidLength = new();
19+
20+
/// <summary>
21+
/// This will be an invalid UInt256 (Integer)
1222
/// </summary>
13-
public static readonly UInt256 Invalid = new();
23+
public static readonly UInt256 InvalidType = new();
1424
}
1525
}

src/Neo.SmartContract.Testing/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,4 +473,3 @@ The currently known limitations are:
473473

474474
- Receive events during the deploy, because the object is returned after performing the deploy, it is not possible to intercept notifications for the deploy unless the contract is previously created with `FromHash` knowing the hash of the contract to be created.
475475
- It is possible that if the contract is updated, the coverage calculation may be incorrect. The update method of a contract can be tested, but if the same script and abi as the original are not used, it can result in a coverage calculation error.
476-
- Some native contracts use the values of `CallingScriptHash` and `EntryScriptHash` for certain actions, such as `CheckWitness`, so overriding the syscalls with `OnGetEntryScriptHash` and `OnGetCallingScriptHash` could fail.

src/Neo.SmartContract.Testing/SmartContract.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using Neo.SmartContract.Testing.Extensions;
2+
using Neo.SmartContract.Testing.Storage;
23
using Neo.VM;
34
using Neo.VM.Types;
45
using System;
56
using System.Collections.Generic;
67
using System.ComponentModel;
78
using System.Linq;
9+
using System.Numerics;
810
using System.Reflection;
911
using System.Runtime.CompilerServices;
1012

@@ -61,11 +63,16 @@ internal StackItem Invoke(string methodName, params object[] args)
6163
{
6264
var arg = args[i];
6365

64-
if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.Invalid) ||
65-
ReferenceEquals(arg, InvalidTypes.InvalidUInt256.Invalid))
66+
if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.InvalidLength) ||
67+
ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidLength))
6668
{
6769
arg = System.Array.Empty<byte>();
6870
}
71+
else if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.InvalidType) ||
72+
ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidType))
73+
{
74+
arg = BigInteger.Zero;
75+
}
6976

7077
script.EmitPush(arg);
7178
}

src/Neo.SmartContract.Testing/SmartContractStorage.cs renamed to src/Neo.SmartContract.Testing/Storage/SmartContractStorage.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using System.Buffers.Binary;
44
using System.Numerics;
55

6-
namespace Neo.SmartContract.Testing
6+
namespace Neo.SmartContract.Testing.Storage
77
{
88
public class SmartContractStorage
99
{
@@ -34,6 +34,18 @@ private int GetContractId()
3434
return _contractId.Value;
3535
}
3636

37+
/// <summary>
38+
/// Check if the entry exist
39+
/// </summary>
40+
/// <param name="key">Key</param>
41+
public bool Contains(byte key) => Contains(new byte[] { key });
42+
43+
/// <summary>
44+
/// Check if the entry exist
45+
/// </summary>
46+
/// <param name="key">Key</param>
47+
public bool Contains(string key) => Contains(Utility.StrictUTF8.GetBytes(key));
48+
3749
/// <summary>
3850
/// Check if the entry exist
3951
/// </summary>
@@ -45,6 +57,18 @@ public bool Contains(ReadOnlyMemory<byte> key)
4557
return entry != null;
4658
}
4759

60+
/// <summary>
61+
/// Read an entry from the smart contract storage
62+
/// </summary>
63+
/// <param name="key">Key</param>
64+
public ReadOnlyMemory<byte> Get(byte key) => Get(new byte[] { key });
65+
66+
/// <summary>
67+
/// Read an entry from the smart contract storage
68+
/// </summary>
69+
/// <param name="key">Key</param>
70+
public ReadOnlyMemory<byte> Get(string key) => Get(Utility.StrictUTF8.GetBytes(key));
71+
4872
/// <summary>
4973
/// Read an entry from the smart contract storage
5074
/// </summary>
@@ -62,6 +86,20 @@ public ReadOnlyMemory<byte> Get(ReadOnlyMemory<byte> key)
6286
return null;
6387
}
6488

89+
/// <summary>
90+
/// Put an entry in the smart contract storage
91+
/// </summary>
92+
/// <param name="key">Key</param>
93+
/// <param name="value">Value</param>
94+
public void Put(byte key, ReadOnlyMemory<byte> value) => Put(new byte[] { key }, value);
95+
96+
/// <summary>
97+
/// Put an entry in the smart contract storage
98+
/// </summary>
99+
/// <param name="key">Key</param>
100+
/// <param name="value">Value</param>
101+
public void Put(string key, ReadOnlyMemory<byte> value) => Put(Utility.StrictUTF8.GetBytes(key), value);
102+
65103
/// <summary>
66104
/// Put an entry in the smart contract storage
67105
/// </summary>
@@ -75,6 +113,20 @@ public void Put(ReadOnlyMemory<byte> key, ReadOnlyMemory<byte> value)
75113
entry.Value = value;
76114
}
77115

116+
/// <summary>
117+
/// Put an entry in the smart contract storage
118+
/// </summary>
119+
/// <param name="key">Key</param>
120+
/// <param name="value">Value</param>
121+
public void Put(byte key, BigInteger value) => Put(new byte[] { key }, value);
122+
123+
/// <summary>
124+
/// Put an entry in the smart contract storage
125+
/// </summary>
126+
/// <param name="key">Key</param>
127+
/// <param name="value">Value</param>
128+
public void Put(string key, BigInteger value) => Put(Utility.StrictUTF8.GetBytes(key), value);
129+
78130
/// <summary>
79131
/// Put an entry in the smart contract storage
80132
/// </summary>
@@ -88,6 +140,18 @@ public void Put(ReadOnlyMemory<byte> key, BigInteger value)
88140
entry.Set(value);
89141
}
90142

143+
/// <summary>
144+
/// Remove an entry from the smart contract storage
145+
/// </summary>
146+
/// <param name="key">Key</param>
147+
public void Remove(byte key) => Remove(new byte[] { key });
148+
149+
/// <summary>
150+
/// Remove an entry from the smart contract storage
151+
/// </summary>
152+
/// <param name="key">Key</param>
153+
public void Remove(string key) => Remove(Utility.StrictUTF8.GetBytes(key));
154+
91155
/// <summary>
92156
/// Remove an entry from the smart contract storage
93157
/// </summary>

src/Neo.SmartContract.Testing/TestEngine.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ namespace Neo.SmartContract.Testing
2222
{
2323
public class TestEngine
2424
{
25+
public delegate UInt160? OnGetScriptHash(UInt160 current, UInt160 expected);
26+
2527
internal readonly Dictionary<UInt160, CoveredContract> Coverage = new();
2628
private readonly Dictionary<UInt160, List<SmartContract>> _contracts = new();
2729
private readonly Dictionary<UInt160, Dictionary<string, CustomMock>> _customMocks = new();
@@ -146,15 +148,15 @@ public UInt160 CommitteeAddress
146148

147149
/// <summary>
148150
/// On GetEntryScriptHash
149-
/// The argument is the ExecutingScriptHash, and it must return the new EntryScriptHash, or null if we don't want to make any change
151+
/// The argument is the ExecutingScriptHash and the expected return, and it must return the new EntryScriptHash, or null if we don't want to make any change
150152
/// </summary>
151-
public Func<UInt160, UInt160?>? OnGetEntryScriptHash { get; set; } = null;
153+
public OnGetScriptHash? OnGetEntryScriptHash { get; set; } = null;
152154

153155
/// <summary>
154156
/// On GetCallingScriptHash
155-
/// The argument is the ExecutingScriptHash, and it must return the new CallingScriptHash, or null if we don't want to make any change
157+
/// The argument is the ExecutingScriptHash and the expected return, and it must return the new CallingScriptHash, or null if we don't want to make any change
156158
/// </summary>
157-
public Func<UInt160, UInt160?>? OnGetCallingScriptHash { get; set; } = null;
159+
public OnGetScriptHash? OnGetCallingScriptHash { get; set; } = null;
158160

159161
/// <summary>
160162
/// Gas

src/Neo.SmartContract.Testing/TestingApplicationEngine.cs

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ internal class TestingApplicationEngine : ApplicationEngine
2525
/// </summary>
2626
public TestEngine Engine { get; }
2727

28+
/// <summary>
29+
/// Override CallingScriptHash
30+
/// </summary>
31+
public override UInt160 CallingScriptHash
32+
{
33+
get
34+
{
35+
var expected = base.CallingScriptHash;
36+
return Engine.OnGetCallingScriptHash?.Invoke(CurrentScriptHash, expected) ?? expected;
37+
}
38+
}
39+
40+
/// <summary>
41+
/// Override EntryScriptHash
42+
/// </summary>
43+
public override UInt160 EntryScriptHash
44+
{
45+
get
46+
{
47+
var expected = base.EntryScriptHash;
48+
return Engine.OnGetEntryScriptHash?.Invoke(CurrentScriptHash, expected) ?? expected;
49+
}
50+
}
51+
2852
public TestingApplicationEngine(TestEngine engine, TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock)
2953
: base(trigger, container, snapshot, persistingBlock, engine.ProtocolSettings, engine.Gas, null)
3054
{
@@ -165,30 +189,8 @@ private void RecoverCoverage(Instruction instruction)
165189

166190
protected override void OnSysCall(InteropDescriptor descriptor)
167191
{
168-
if (descriptor == System_Runtime_GetEntryScriptHash)
169-
{
170-
var currentHash = InstructionContext.GetScriptHash();
171-
var hash = Engine.OnGetEntryScriptHash?.Invoke(currentHash);
172-
173-
if (hash is not null)
174-
{
175-
Push(Convert(hash));
176-
return;
177-
}
178-
}
179-
else if (descriptor == System_Runtime_GetCallingScriptHash)
180-
{
181-
var currentHash = InstructionContext.GetScriptHash();
182-
var hash = Engine.OnGetCallingScriptHash?.Invoke(currentHash);
183-
184-
if (hash is not null)
185-
{
186-
Push(Convert(hash));
187-
return;
188-
}
189-
}
190192
// descriptor.Hash == 1381727586 && descriptor.Name == "System.Contract.Call" && descriptor.Parameters.Count == 4)
191-
else if (descriptor == System_Contract_Call)
193+
if (descriptor == System_Contract_Call)
192194
{
193195
// Check if the syscall is a contract call and we need to mock it because it was defined by the user
194196

src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ public virtual void TestBalanceOf()
122122
{
123123
Assert.AreEqual(0, Contract.BalanceOf(Bob.Account));
124124
Assert.ThrowsException<VMUnhandledException>(() => Contract.BalanceOf(InvalidUInt160.Null));
125-
Assert.ThrowsException<VMUnhandledException>(() => Contract.BalanceOf(InvalidUInt160.Invalid));
125+
Assert.ThrowsException<VMUnhandledException>(() => Contract.BalanceOf(InvalidUInt160.InvalidLength));
126+
Assert.ThrowsException<VMUnhandledException>(() => Contract.BalanceOf(InvalidUInt160.InvalidType));
126127
}
127128

128129
[TestMethod]
@@ -156,8 +157,10 @@ public virtual void TestTransfer()
156157
Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.Null, 0)));
157158

158159
Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(Alice.Account, Bob.Account, -1)));
159-
Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(InvalidUInt160.Invalid, Bob.Account, -1)));
160-
Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.Invalid, 0)));
160+
Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(InvalidUInt160.InvalidLength, Bob.Account, -1)));
161+
Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(InvalidUInt160.InvalidType, Bob.Account, -1)));
162+
Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.InvalidLength, 0)));
163+
Assert.ThrowsException<VMUnhandledException>(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.InvalidType, 0)));
161164

162165
// Invoke transfer without signature
163166

src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ public virtual void TestSetGetOwner()
9898
Engine.SetTransactionSigners(Alice);
9999
Assert.ThrowsException<Exception>(() => Contract.Owner = UInt160.Zero);
100100
Assert.ThrowsException<InvalidOperationException>(() => Contract.Owner = InvalidUInt160.Null);
101-
Assert.ThrowsException<Exception>(() => Contract.Owner = InvalidUInt160.Invalid);
101+
Assert.ThrowsException<Exception>(() => Contract.Owner = InvalidUInt160.InvalidLength);
102+
Assert.ThrowsException<Exception>(() => Contract.Owner = InvalidUInt160.InvalidType);
102103

103104
Contract.Owner = Bob.Account;
104105
Assert.AreEqual(Bob.Account, Contract.Owner);

tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ public void TestDeployWithOwner()
163163
// Try with invalid owners
164164

165165
Assert.ThrowsException<Exception>(() => Engine.Deploy<Nep17Contract>(NefFile, Manifest, UInt160.Zero));
166-
Assert.ThrowsException<Exception>(() => Engine.Deploy<Nep17Contract>(NefFile, Manifest, InvalidUInt160.Invalid));
166+
Assert.ThrowsException<Exception>(() => Engine.Deploy<Nep17Contract>(NefFile, Manifest, InvalidUInt160.InvalidLength));
167+
Assert.ThrowsException<Exception>(() => Engine.Deploy<Nep17Contract>(NefFile, Manifest, InvalidUInt160.InvalidType));
167168

168169
// Test SetOwner notification
169170

0 commit comments

Comments
 (0)