diff --git a/src/Neo/SmartContract/KeyBuilder.cs b/src/Neo/SmartContract/KeyBuilder.cs index 76cc95b107..2270c2a8a9 100644 --- a/src/Neo/SmartContract/KeyBuilder.cs +++ b/src/Neo/SmartContract/KeyBuilder.cs @@ -22,8 +22,6 @@ namespace Neo.SmartContract; /// public class KeyBuilder : IEnumerable { - public const int PrefixLength = sizeof(int) + sizeof(byte); - private readonly byte[] _cacheData; private int _keyLength; @@ -105,6 +103,65 @@ public KeyBuilder Add(T key) where T : unmanaged return Add(data); } + internal static bool TryParse(byte[] data, Type[] types, out int id, out byte prefix, out object[] values) + { + values = Array.Empty(); + + foreach (var t in types) + { + if (!t.IsValueType || t.IsGenericType) + { + id = default; + prefix = default; + return false; + } + } + + if (data.Length < sizeof(int) + 1) + { + id = default; + prefix = default; + return false; + } + + id = BinaryPrimitives.ReadInt32LittleEndian(data); + prefix = data[sizeof(int)]; + + values = new object[types.Length]; + int offset = sizeof(int) + 1; + + for (int i = 0; i < types.Length; i++) + { + int size = Marshal.SizeOf(types[i]); + if (data.Length < offset + size) + return false; + + Span span = data.AsSpan(offset, size); + + if (BitConverter.IsLittleEndian) + span.Reverse(); + + values[i] = ReadStruct(span, types[i]); + offset += size; + } + + return true; + } + + static object ReadStruct(Span span, Type t) + { + byte[] buffer = span.ToArray(); + var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + return Marshal.PtrToStructure(handle.AddrOfPinnedObject(), t)!; + } + finally + { + handle.Free(); + } + } + /// /// Gets the storage key generated by the builder. /// diff --git a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs index caccc45fb3..3c0e5bd0a0 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs @@ -11,6 +11,7 @@ using Neo.IO; using Neo.SmartContract; +using System.Reflection; namespace Neo.UnitTests.SmartContract; @@ -42,6 +43,30 @@ public void Test() Assert.AreEqual("010000000000000001", key.ToArray().ToHexString()); } + [TestMethod] + public void TestTryParse() + { + var key = new KeyBuilder(1, 2); + Assert.AreEqual("0100000002", key.ToArray().ToHexString()); + + // add int + key = new KeyBuilder(1, 2); + key = key.Add(-1); + key = key.Add(2); + Assert.AreEqual("0100000002ffffffff00000002", key.ToArray().ToHexString()); + + Assert.IsFalse(KeyBuilder.TryParse([], [typeof(int), typeof(int)], out int id, out byte prefix, out var ret)); + Assert.IsFalse(KeyBuilder.TryParse([0, 1, 2, 3, 4, 5], [typeof(int), typeof(int)], out id, out prefix, out ret)); + Assert.IsTrue(KeyBuilder.TryParse(key.ToArray(), [typeof(int), typeof(int)], out id, out prefix, out ret)); + + Assert.AreEqual(1, id); + Assert.AreEqual(2, prefix); + Assert.IsNotNull(ret); + Assert.HasCount(2, ret); + Assert.AreEqual(-1, ret[0]); + Assert.AreEqual(2, ret[1]); + } + [TestMethod] public void TestAddInt() {