diff --git a/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt160.cs b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt160.cs
index c578928aa..b3de122b9 100644
--- a/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt160.cs
+++ b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt160.cs
@@ -2,14 +2,24 @@ namespace Neo.SmartContract.Testing.InvalidTypes
{
public class InvalidUInt160
{
+ ///
+ /// Zero
+ ///
+ public static readonly UInt160 Zero = UInt160.Zero;
+
///
/// Null UInt160
///
public static readonly UInt160? Null = null;
///
- /// This will be an invalid UInt160
+ /// This will be an invalid UInt160 (ByteString)
+ ///
+ public static readonly UInt160 InvalidLength = new();
+
+ ///
+ /// This will be an invalid UInt160 (Integer)
///
- public static readonly UInt160 Invalid = new();
+ public static readonly UInt160 InvalidType = new();
}
}
diff --git a/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt256.cs b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt256.cs
index 90a63ab8b..72f3bcd9c 100644
--- a/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt256.cs
+++ b/src/Neo.SmartContract.Testing/InvalidTypes/InvalidUInt256.cs
@@ -2,14 +2,24 @@ namespace Neo.SmartContract.Testing.InvalidTypes
{
public class InvalidUInt256
{
+ ///
+ /// Zero
+ ///
+ public static readonly UInt160 Zero = UInt160.Zero;
+
///
/// Null UInt256
///
public static readonly UInt256? Null = null;
///
- /// This will be an invalid UInt256
+ /// This will be an invalid UInt256 (ByteString)
+ ///
+ public static readonly UInt256 InvalidLength = new();
+
+ ///
+ /// This will be an invalid UInt256 (Integer)
///
- public static readonly UInt256 Invalid = new();
+ public static readonly UInt256 InvalidType = new();
}
}
diff --git a/src/Neo.SmartContract.Testing/README.md b/src/Neo.SmartContract.Testing/README.md
index d8613ceb0..5c0e89ac6 100644
--- a/src/Neo.SmartContract.Testing/README.md
+++ b/src/Neo.SmartContract.Testing/README.md
@@ -473,4 +473,3 @@ The currently known limitations are:
- 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.
- 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.
-- 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.
diff --git a/src/Neo.SmartContract.Testing/SmartContract.cs b/src/Neo.SmartContract.Testing/SmartContract.cs
index 96dc93fe5..38360cbbf 100644
--- a/src/Neo.SmartContract.Testing/SmartContract.cs
+++ b/src/Neo.SmartContract.Testing/SmartContract.cs
@@ -1,10 +1,12 @@
using Neo.SmartContract.Testing.Extensions;
+using Neo.SmartContract.Testing.Storage;
using Neo.VM;
using Neo.VM.Types;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
+using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
@@ -61,11 +63,16 @@ internal StackItem Invoke(string methodName, params object[] args)
{
var arg = args[i];
- if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.Invalid) ||
- ReferenceEquals(arg, InvalidTypes.InvalidUInt256.Invalid))
+ if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.InvalidLength) ||
+ ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidLength))
{
arg = System.Array.Empty();
}
+ else if (ReferenceEquals(arg, InvalidTypes.InvalidUInt160.InvalidType) ||
+ ReferenceEquals(arg, InvalidTypes.InvalidUInt256.InvalidType))
+ {
+ arg = BigInteger.Zero;
+ }
script.EmitPush(arg);
}
diff --git a/src/Neo.SmartContract.Testing/SmartContractStorage.cs b/src/Neo.SmartContract.Testing/Storage/SmartContractStorage.cs
similarity index 69%
rename from src/Neo.SmartContract.Testing/SmartContractStorage.cs
rename to src/Neo.SmartContract.Testing/Storage/SmartContractStorage.cs
index 9ea108558..a521471a6 100644
--- a/src/Neo.SmartContract.Testing/SmartContractStorage.cs
+++ b/src/Neo.SmartContract.Testing/Storage/SmartContractStorage.cs
@@ -3,7 +3,7 @@
using System.Buffers.Binary;
using System.Numerics;
-namespace Neo.SmartContract.Testing
+namespace Neo.SmartContract.Testing.Storage
{
public class SmartContractStorage
{
@@ -34,6 +34,18 @@ private int GetContractId()
return _contractId.Value;
}
+ ///
+ /// Check if the entry exist
+ ///
+ /// Key
+ public bool Contains(byte key) => Contains(new byte[] { key });
+
+ ///
+ /// Check if the entry exist
+ ///
+ /// Key
+ public bool Contains(string key) => Contains(Utility.StrictUTF8.GetBytes(key));
+
///
/// Check if the entry exist
///
@@ -45,6 +57,18 @@ public bool Contains(ReadOnlyMemory key)
return entry != null;
}
+ ///
+ /// Read an entry from the smart contract storage
+ ///
+ /// Key
+ public ReadOnlyMemory Get(byte key) => Get(new byte[] { key });
+
+ ///
+ /// Read an entry from the smart contract storage
+ ///
+ /// Key
+ public ReadOnlyMemory Get(string key) => Get(Utility.StrictUTF8.GetBytes(key));
+
///
/// Read an entry from the smart contract storage
///
@@ -62,6 +86,20 @@ public ReadOnlyMemory Get(ReadOnlyMemory key)
return null;
}
+ ///
+ /// Put an entry in the smart contract storage
+ ///
+ /// Key
+ /// Value
+ public void Put(byte key, ReadOnlyMemory value) => Put(new byte[] { key }, value);
+
+ ///
+ /// Put an entry in the smart contract storage
+ ///
+ /// Key
+ /// Value
+ public void Put(string key, ReadOnlyMemory value) => Put(Utility.StrictUTF8.GetBytes(key), value);
+
///
/// Put an entry in the smart contract storage
///
@@ -75,6 +113,20 @@ public void Put(ReadOnlyMemory key, ReadOnlyMemory value)
entry.Value = value;
}
+ ///
+ /// Put an entry in the smart contract storage
+ ///
+ /// Key
+ /// Value
+ public void Put(byte key, BigInteger value) => Put(new byte[] { key }, value);
+
+ ///
+ /// Put an entry in the smart contract storage
+ ///
+ /// Key
+ /// Value
+ public void Put(string key, BigInteger value) => Put(Utility.StrictUTF8.GetBytes(key), value);
+
///
/// Put an entry in the smart contract storage
///
@@ -88,6 +140,18 @@ public void Put(ReadOnlyMemory key, BigInteger value)
entry.Set(value);
}
+ ///
+ /// Remove an entry from the smart contract storage
+ ///
+ /// Key
+ public void Remove(byte key) => Remove(new byte[] { key });
+
+ ///
+ /// Remove an entry from the smart contract storage
+ ///
+ /// Key
+ public void Remove(string key) => Remove(Utility.StrictUTF8.GetBytes(key));
+
///
/// Remove an entry from the smart contract storage
///
diff --git a/src/Neo.SmartContract.Testing/TestEngine.cs b/src/Neo.SmartContract.Testing/TestEngine.cs
index e87236c6f..aecfff398 100644
--- a/src/Neo.SmartContract.Testing/TestEngine.cs
+++ b/src/Neo.SmartContract.Testing/TestEngine.cs
@@ -22,6 +22,8 @@ namespace Neo.SmartContract.Testing
{
public class TestEngine
{
+ public delegate UInt160? OnGetScriptHash(UInt160 current, UInt160 expected);
+
internal readonly Dictionary Coverage = new();
private readonly Dictionary> _contracts = new();
private readonly Dictionary> _customMocks = new();
@@ -146,15 +148,15 @@ public UInt160 CommitteeAddress
///
/// On GetEntryScriptHash
- /// The argument is the ExecutingScriptHash, and it must return the new EntryScriptHash, or null if we don't want to make any change
+ /// 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
///
- public Func? OnGetEntryScriptHash { get; set; } = null;
+ public OnGetScriptHash? OnGetEntryScriptHash { get; set; } = null;
///
/// On GetCallingScriptHash
- /// The argument is the ExecutingScriptHash, and it must return the new CallingScriptHash, or null if we don't want to make any change
+ /// 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
///
- public Func? OnGetCallingScriptHash { get; set; } = null;
+ public OnGetScriptHash? OnGetCallingScriptHash { get; set; } = null;
///
/// Gas
diff --git a/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs b/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs
index 2417bfb6d..f0f81c6a6 100644
--- a/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs
+++ b/src/Neo.SmartContract.Testing/TestingApplicationEngine.cs
@@ -25,6 +25,30 @@ internal class TestingApplicationEngine : ApplicationEngine
///
public TestEngine Engine { get; }
+ ///
+ /// Override CallingScriptHash
+ ///
+ public override UInt160 CallingScriptHash
+ {
+ get
+ {
+ var expected = base.CallingScriptHash;
+ return Engine.OnGetCallingScriptHash?.Invoke(CurrentScriptHash, expected) ?? expected;
+ }
+ }
+
+ ///
+ /// Override EntryScriptHash
+ ///
+ public override UInt160 EntryScriptHash
+ {
+ get
+ {
+ var expected = base.EntryScriptHash;
+ return Engine.OnGetEntryScriptHash?.Invoke(CurrentScriptHash, expected) ?? expected;
+ }
+ }
+
public TestingApplicationEngine(TestEngine engine, TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock)
: base(trigger, container, snapshot, persistingBlock, engine.ProtocolSettings, engine.Gas, null)
{
@@ -165,30 +189,8 @@ private void RecoverCoverage(Instruction instruction)
protected override void OnSysCall(InteropDescriptor descriptor)
{
- if (descriptor == System_Runtime_GetEntryScriptHash)
- {
- var currentHash = InstructionContext.GetScriptHash();
- var hash = Engine.OnGetEntryScriptHash?.Invoke(currentHash);
-
- if (hash is not null)
- {
- Push(Convert(hash));
- return;
- }
- }
- else if (descriptor == System_Runtime_GetCallingScriptHash)
- {
- var currentHash = InstructionContext.GetScriptHash();
- var hash = Engine.OnGetCallingScriptHash?.Invoke(currentHash);
-
- if (hash is not null)
- {
- Push(Convert(hash));
- return;
- }
- }
// descriptor.Hash == 1381727586 && descriptor.Name == "System.Contract.Call" && descriptor.Parameters.Count == 4)
- else if (descriptor == System_Contract_Call)
+ if (descriptor == System_Contract_Call)
{
// Check if the syscall is a contract call and we need to mock it because it was defined by the user
diff --git a/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs b/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs
index b28ea28f3..1571b331c 100644
--- a/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs
+++ b/src/Neo.SmartContract.Testing/TestingStandards/Nep17Tests.cs
@@ -122,7 +122,8 @@ public virtual void TestBalanceOf()
{
Assert.AreEqual(0, Contract.BalanceOf(Bob.Account));
Assert.ThrowsException(() => Contract.BalanceOf(InvalidUInt160.Null));
- Assert.ThrowsException(() => Contract.BalanceOf(InvalidUInt160.Invalid));
+ Assert.ThrowsException(() => Contract.BalanceOf(InvalidUInt160.InvalidLength));
+ Assert.ThrowsException(() => Contract.BalanceOf(InvalidUInt160.InvalidType));
}
[TestMethod]
@@ -156,8 +157,10 @@ public virtual void TestTransfer()
Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.Null, 0)));
Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, Bob.Account, -1)));
- Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(InvalidUInt160.Invalid, Bob.Account, -1)));
- Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.Invalid, 0)));
+ Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(InvalidUInt160.InvalidLength, Bob.Account, -1)));
+ Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(InvalidUInt160.InvalidType, Bob.Account, -1)));
+ Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.InvalidLength, 0)));
+ Assert.ThrowsException(() => Assert.IsTrue(Contract.Transfer(Alice.Account, InvalidUInt160.InvalidType, 0)));
// Invoke transfer without signature
diff --git a/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs b/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs
index 3ab6cf3bb..7a264c42e 100644
--- a/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs
+++ b/src/Neo.SmartContract.Testing/TestingStandards/OwnableTests.cs
@@ -98,7 +98,8 @@ public virtual void TestSetGetOwner()
Engine.SetTransactionSigners(Alice);
Assert.ThrowsException(() => Contract.Owner = UInt160.Zero);
Assert.ThrowsException(() => Contract.Owner = InvalidUInt160.Null);
- Assert.ThrowsException(() => Contract.Owner = InvalidUInt160.Invalid);
+ Assert.ThrowsException(() => Contract.Owner = InvalidUInt160.InvalidLength);
+ Assert.ThrowsException(() => Contract.Owner = InvalidUInt160.InvalidType);
Contract.Owner = Bob.Account;
Assert.AreEqual(Bob.Account, Contract.Owner);
diff --git a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs
index b5c2d2b53..de1c4de00 100644
--- a/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs
+++ b/tests/Neo.SmartContract.Template.UnitTests/templates/neocontractnep17/Nep17ContractTests.cs
@@ -163,7 +163,8 @@ public void TestDeployWithOwner()
// Try with invalid owners
Assert.ThrowsException(() => Engine.Deploy(NefFile, Manifest, UInt160.Zero));
- Assert.ThrowsException(() => Engine.Deploy(NefFile, Manifest, InvalidUInt160.Invalid));
+ Assert.ThrowsException(() => Engine.Deploy(NefFile, Manifest, InvalidUInt160.InvalidLength));
+ Assert.ThrowsException(() => Engine.Deploy(NefFile, Manifest, InvalidUInt160.InvalidType));
// Test SetOwner notification
diff --git a/tests/Neo.SmartContract.Testing.UnitTests/Storage/TestStorageTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/Storage/TestStorageTests.cs
index 6b085ecd4..1b024a5c1 100644
--- a/tests/Neo.SmartContract.Testing.UnitTests/Storage/TestStorageTests.cs
+++ b/tests/Neo.SmartContract.Testing.UnitTests/Storage/TestStorageTests.cs
@@ -21,6 +21,7 @@ public void TestCheckpoint()
// Check that all it works
+ Assert.IsTrue(engine.Native.NEO.Storage.Contains(1)); // Prefix_VotersCount
Assert.AreEqual(100_000_000, engine.Native.NEO.TotalSupply);
// Create checkpoint
diff --git a/tests/Neo.SmartContract.Testing.UnitTests/TestEngineTests.cs b/tests/Neo.SmartContract.Testing.UnitTests/TestEngineTests.cs
index 046044776..c29debcc9 100644
--- a/tests/Neo.SmartContract.Testing.UnitTests/TestEngineTests.cs
+++ b/tests/Neo.SmartContract.Testing.UnitTests/TestEngineTests.cs
@@ -42,7 +42,7 @@ public void TestOnGetEntryScriptHash()
Assert.AreEqual("0xfa99b1aeedab84a47856358515e7f982341aa767", engine.Execute(script).ConvertTo(typeof(UInt160)).ToString());
- engine.OnGetEntryScriptHash = current => UInt160.Parse("0x0000000000000000000000000000000000000001");
+ engine.OnGetEntryScriptHash = (current, expected) => UInt160.Parse("0x0000000000000000000000000000000000000001");
Assert.AreEqual("0x0000000000000000000000000000000000000001", engine.Execute(script).ConvertTo(typeof(UInt160)).ToString());
}
@@ -57,7 +57,7 @@ public void TestOnGetCallingScriptHash()
Assert.AreEqual(StackItem.Null, engine.Execute(script));
- engine.OnGetCallingScriptHash = current => UInt160.Parse("0x0000000000000000000000000000000000000001");
+ engine.OnGetCallingScriptHash = (current, expected) => UInt160.Parse("0x0000000000000000000000000000000000000001");
Assert.AreEqual("0x0000000000000000000000000000000000000001", engine.Execute(script).ConvertTo(typeof(UInt160)).ToString());
}