Skip to content

Commit

Permalink
TestEngine: Some fixes in TestEngine (neo-project#934)
Browse files Browse the repository at this point in the history
* Fix mock properties

* Simplify class conversion

* Add value types and fix namespace
  • Loading branch information
shargon authored and cschuchardt88 committed Mar 21, 2024
1 parent 904b539 commit 0cb982a
Show file tree
Hide file tree
Showing 18 changed files with 198 additions and 68 deletions.
21 changes: 21 additions & 0 deletions src/Neo.SmartContract.Testing/Attributes/FieldOrderAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace Neo.SmartContract.Testing.Attributes;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FieldOrderAttribute : Attribute
{
/// <summary>
/// Gets the deserialization order of the property.
/// </summary>
public int Order { get; }

/// <summary>
/// Constructor
/// </summary>
/// <param name="order">Order</param>
public FieldOrderAttribute(int order)
{
Order = order;
}
}
108 changes: 102 additions & 6 deletions src/Neo.SmartContract.Testing/Extensions/TestExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Akka.Util;
using Neo.Cryptography.ECC;
using Neo.SmartContract.Iterators;
using Neo.SmartContract.Testing.Attributes;
using Neo.VM.Types;
using System;
using System.Collections.Generic;
Expand All @@ -11,6 +12,9 @@ namespace Neo.SmartContract.Testing.Extensions
{
public static class TestExtensions
{
private static readonly Dictionary<Type, Dictionary<int, PropertyInfo>> _propertyCache = new();
private static readonly Dictionary<Type, FieldInfo[]> _fieldCache = new();

/// <summary>
/// Convert Array stack item to dotnet array
/// </summary>
Expand Down Expand Up @@ -75,18 +79,84 @@ public static class TestExtensions
_ when type == typeof(UInt160) => new UInt160(stackItem.GetSpan().ToArray()),
_ when type == typeof(UInt256) => new UInt256(stackItem.GetSpan().ToArray()),
_ when type == typeof(ECPoint) => ECPoint.FromBytes(stackItem.GetSpan().ToArray(), ECCurve.Secp256r1),
_ when type == typeof(IDictionary<object, object>) && stackItem is Map mp => ToDictionary(mp), // SubItems in StackItem type
_ when type == typeof(Dictionary<object, object>) && stackItem is Map mp => ToDictionary(mp), // SubItems in StackItem type
_ when type == typeof(IList<object>) && stackItem is CompoundType cp => new List<object>(cp.SubItems), // SubItems in StackItem type
_ when type == typeof(List<object>) && stackItem is CompoundType cp => new List<object>(cp.SubItems), // SubItems in StackItem type
_ when typeof(IInteroperable).IsAssignableFrom(type) => CreateInteroperable(stackItem, type),
_ when type.IsArray && stackItem is CompoundType cp => CreateTypeArray(cp.SubItems, type.GetElementType()!),
_ when stackItem is InteropInterface it && it.GetInterface().GetType() == type => it.GetInterface(),

_ when stackItem is VM.Types.Array ar => type switch
{
_ when type == typeof(IList<object>) => new List<object>(ar.SubItems), // SubItems in StackItem type
_ when type == typeof(List<object>) => new List<object>(ar.SubItems), // SubItems in StackItem type
_ when type.IsArray => CreateTypeArray(ar.SubItems, type.GetElementType()!),
_ when type.IsClass => CreateObject(ar.SubItems, type),
_ when type.IsValueType => CreateValueType(ar.SubItems, type),
_ => throw new FormatException($"Impossible to convert {stackItem} to {type}"),
},
_ when stackItem is Map mp => type switch
{
_ when type == typeof(IDictionary<object, object>) => ToDictionary(mp), // SubItems in StackItem type
_ when type == typeof(Dictionary<object, object>) => ToDictionary(mp), // SubItems in StackItem type
_ => throw new FormatException($"Impossible to convert {stackItem} to {type}"),
},

_ => throw new FormatException($"Impossible to convert {stackItem} to {type}"),
};
}

private static object CreateObject(IEnumerable<StackItem> subItems, Type type)
{
var index = 0;
var obj = Activator.CreateInstance(type) ?? throw new FormatException($"Impossible create {type}");

// Cache the object properties by offset

if (!_propertyCache.TryGetValue(type, out var cache))
{
cache = new Dictionary<int, PropertyInfo>();

foreach (var property in type.GetProperties())
{
var fieldOffset = property.GetCustomAttribute<FieldOrderAttribute>();
if (fieldOffset is null) continue;
if (!property.CanWrite) continue;

cache.Add(fieldOffset.Order, property);
}

if (cache.Count == 0)
{
// Without FieldOrderAttribute, by order

foreach (var property in type.GetProperties())
{
if (!property.CanWrite) continue;
cache.Add(index, property);
index++;
}
index = 0;
}

_propertyCache[type] = cache;
}

// Fill the object

foreach (var item in subItems)
{
if (cache.TryGetValue(index, out var property))
{
property.SetValue(obj, ConvertTo(item, property.PropertyType));
}
else
{
throw new FormatException($"Error converting {type}, the property with the offset {index} was not found.");
}

index++;
}

return obj;
}

private static IDictionary<object, object> ToDictionary(Map map)
{
Dictionary<object, object> dictionary = new();
Expand All @@ -99,6 +169,32 @@ private static IDictionary<object, object> ToDictionary(Map map)
return dictionary;
}

private static object CreateValueType(IEnumerable<StackItem> objects, Type valueType)
{
var arr = objects.ToArray();
var value = Activator.CreateInstance(valueType);

// Cache the object properties by offset

if (!_fieldCache.TryGetValue(valueType, out var cache))
{
cache = valueType.GetFields().ToArray();
_fieldCache[valueType] = cache;
}

if (cache.Length != arr.Length)
{
throw new FormatException($"Error converting {valueType}, field count doesn't match.");
}

for (int x = 0; x < arr.Length; x++)
{
cache[x].SetValue(value, ConvertTo(arr[x], cache[x].FieldType));
}

return value;
}

private static object CreateTypeArray(IEnumerable<StackItem> objects, Type elementType)
{
var obj = objects.ToArray();
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Native/ContractManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class ContractManagement : SmartContract
{
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Native/CryptoLib.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class CryptoLib : SmartContract
{
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Native/GasToken.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class GasToken : SmartContract
{
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Native/LedgerContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class LedgerContract : SmartContract
{
Expand Down
35 changes: 8 additions & 27 deletions src/Neo.SmartContract.Testing/Native/NeoToken.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,26 @@
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.SmartContract.Iterators;
using Neo.VM;
using Neo.VM.Types;
using System;
using Neo.SmartContract.Testing.Attributes;
using System.ComponentModel;
using System.Numerics;
using Neo.SmartContract.Testing.Extensions;
using System.Linq;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class NeoToken : SmartContract
{
public class Candidate : IInteroperable
public class Candidate
{
/// <summary>
/// Public key
/// </summary>
public ECPoint? PublicKey { get; private set; }
[FieldOrder(0)]
public ECPoint? PublicKey { get; set; }

/// <summary>
/// Votes
/// </summary>
public BigInteger Votes { get; private set; } = BigInteger.Zero;

public void FromStackItem(StackItem stackItem)
{
if (stackItem is not CompoundType cp) throw new FormatException();
if (cp.Count < 2) throw new FormatException();

var items = cp.SubItems.ToArray();

PublicKey = (ECPoint)items[0].ConvertTo(typeof(ECPoint))!;
Votes = (BigInteger)items[1].ConvertTo(typeof(BigInteger))!;
}

public StackItem ToStackItem(ReferenceCounter referenceCounter)
{
return new VM.Types.Array(new StackItem[] { PublicKey.ToArray(), Votes });
}
[FieldOrder(1)]
public BigInteger Votes { get; set; }
}

#region Events
Expand Down Expand Up @@ -119,7 +100,7 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter)
/// Safe method
/// </summary>
[DisplayName("getAccountState")]
public abstract Native.NeoToken.NeoAccountState GetAccountState(UInt160? account);
public abstract Neo.SmartContract.Native.NeoToken.NeoAccountState GetAccountState(UInt160? account);

/// <summary>
/// Safe method
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Native/OracleContract.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class OracleContract : SmartContract
{
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Native/PolicyContract.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class PolicyContract : SmartContract
{
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Native/RoleManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class RoleManagement : SmartContract
{
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Native/StdLib.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Numerics;

namespace Neo.SmartContract.Testing;
namespace Neo.SmartContract.Testing.Native;

public abstract class StdLib : SmartContract
{
Expand Down
29 changes: 15 additions & 14 deletions src/Neo.SmartContract.Testing/NativeArtifacts.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Neo.Persistence;
using Neo.SmartContract.Testing.Native;
using System;
using System.Reflection;

Expand Down Expand Up @@ -64,15 +65,15 @@ public NativeArtifacts(TestEngine engine)
{
_engine = engine;

ContractManagement = _engine.FromHash<ContractManagement>(Native.NativeContract.ContractManagement.Hash, Native.NativeContract.ContractManagement.Id);
CryptoLib = _engine.FromHash<CryptoLib>(Native.NativeContract.CryptoLib.Hash, Native.NativeContract.CryptoLib.Id);
GAS = _engine.FromHash<GasToken>(Native.NativeContract.GAS.Hash, Native.NativeContract.GAS.Id);
NEO = _engine.FromHash<NeoToken>(Native.NativeContract.NEO.Hash, Native.NativeContract.NEO.Id);
Ledger = _engine.FromHash<LedgerContract>(Native.NativeContract.Ledger.Hash, Native.NativeContract.Ledger.Id);
Oracle = _engine.FromHash<OracleContract>(Native.NativeContract.Oracle.Hash, Native.NativeContract.Oracle.Id);
Policy = _engine.FromHash<PolicyContract>(Native.NativeContract.Policy.Hash, Native.NativeContract.Policy.Id);
RoleManagement = _engine.FromHash<RoleManagement>(Native.NativeContract.RoleManagement.Hash, Native.NativeContract.RoleManagement.Id);
StdLib = _engine.FromHash<StdLib>(Native.NativeContract.StdLib.Hash, Native.NativeContract.StdLib.Id);
ContractManagement = _engine.FromHash<ContractManagement>(Neo.SmartContract.Native.NativeContract.ContractManagement.Hash, Neo.SmartContract.Native.NativeContract.ContractManagement.Id);
CryptoLib = _engine.FromHash<CryptoLib>(Neo.SmartContract.Native.NativeContract.CryptoLib.Hash, Neo.SmartContract.Native.NativeContract.CryptoLib.Id);
GAS = _engine.FromHash<GasToken>(Neo.SmartContract.Native.NativeContract.GAS.Hash, Neo.SmartContract.Native.NativeContract.GAS.Id);
NEO = _engine.FromHash<NeoToken>(Neo.SmartContract.Native.NativeContract.NEO.Hash, Neo.SmartContract.Native.NativeContract.NEO.Id);
Ledger = _engine.FromHash<LedgerContract>(Neo.SmartContract.Native.NativeContract.Ledger.Hash, Neo.SmartContract.Native.NativeContract.Ledger.Id);
Oracle = _engine.FromHash<OracleContract>(Neo.SmartContract.Native.NativeContract.Oracle.Hash, Neo.SmartContract.Native.NativeContract.Oracle.Id);
Policy = _engine.FromHash<PolicyContract>(Neo.SmartContract.Native.NativeContract.Policy.Hash, Neo.SmartContract.Native.NativeContract.Policy.Id);
RoleManagement = _engine.FromHash<RoleManagement>(Neo.SmartContract.Native.NativeContract.RoleManagement.Hash, Neo.SmartContract.Native.NativeContract.RoleManagement.Id);
StdLib = _engine.FromHash<StdLib>(Neo.SmartContract.Native.NativeContract.StdLib.Hash, Neo.SmartContract.Native.NativeContract.StdLib.Id);
}

/// <summary>
Expand All @@ -92,12 +93,12 @@ public void Initialize(bool commit = false)

// Process native contracts

foreach (var native in new Native.NativeContract[]
foreach (var native in new Neo.SmartContract.Native.NativeContract[]
{
Native.NativeContract.ContractManagement,
Native.NativeContract.Ledger,
Native.NativeContract.NEO,
Native.NativeContract.GAS
Neo.SmartContract.Native.NativeContract.ContractManagement,
Neo.SmartContract.Native.NativeContract.Ledger,
Neo.SmartContract.Native.NativeContract.NEO,
Neo.SmartContract.Native.NativeContract.GAS
}
)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Testing/Storage/EngineStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Neo.SmartContract.Testing.Storage
public class EngineStorage
{
// Key to check if native contracts are initialized, by default: Neo.votersCountPrefix
private static readonly StorageKey _initKey = new() { Id = Native.NativeContract.NEO.Id, Key = new byte[] { 1 } };
private static readonly StorageKey _initKey = new() { Id = Neo.SmartContract.Native.NativeContract.NEO.Id, Key = new byte[] { 1 } };

/// <summary>
/// Store
Expand Down
5 changes: 4 additions & 1 deletion src/Neo.SmartContract.Testing/TestEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Neo.VM.Types;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
Expand Down Expand Up @@ -394,7 +395,9 @@ private T MockContract<T>(UInt160 hash, int? contractId = null, Action<Mock<T>>?

if (mock.IsMocked(method))
{
var mockName = method.Name + ";" + method.GetParameters().Length;
var display = method.GetCustomAttribute<DisplayNameAttribute>();
var name = display is not null ? display.DisplayName : method.Name;
var mockName = name + ";" + method.GetParameters().Length;
var cm = new CustomMock(mock.Object, method);

if (_customMocks.TryGetValue(hash, out var mocks))
Expand Down
3 changes: 2 additions & 1 deletion src/Neo.SmartContract.Testing/TestingApplicationEngine.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract.Native;
using Neo.SmartContract.Testing.Extensions;
using Neo.VM;
using Neo.VM.Types;
Expand Down Expand Up @@ -76,7 +77,7 @@ private void RecoverCoverage(Instruction instruction)
{
// We need the contract state without pay gas

var state = Native.NativeContract.ContractManagement.GetContract(Engine.Storage.Snapshot, contractHash);
var state = NativeContract.ContractManagement.GetContract(Engine.Storage.Snapshot, contractHash);

coveredContract = new(Engine.MethodDetection, contractHash, state);
Engine.Coverage[contractHash] = coveredContract;
Expand Down
Loading

0 comments on commit 0cb982a

Please sign in to comment.