From 2db4541012a7a4f697f860681825e01c4c909f7e Mon Sep 17 00:00:00 2001 From: David Hernando Date: Sun, 1 Mar 2026 23:28:06 +0100 Subject: [PATCH] Added deserialization of named args, and list of named args. Signed-off-by: David Hernando --- .../CLValueByteSerializerTest.cs | 216 +++++++++++++++++- .../CLValueDictionaryTest.cs | 40 ++++ .../NamedArgJsonSerializerTest.cs | 156 +++++++++++++ .../ByteSerializers/CLValueByteSerializer.cs | 41 +++- .../ByteSerializers/NamedArgByteSerializer.cs | 18 +- Casper.Network.SDK/Types/CLValueDictionary.cs | 68 ++++++ Casper.Network.SDK/Types/NamedArg.cs | 48 +++- 7 files changed, 574 insertions(+), 13 deletions(-) create mode 100644 Casper.Network.SDK.Test/CLValueDictionaryTest.cs create mode 100644 Casper.Network.SDK/Types/CLValueDictionary.cs diff --git a/Casper.Network.SDK.Test/CLValueByteSerializerTest.cs b/Casper.Network.SDK.Test/CLValueByteSerializerTest.cs index e42e789..7a63625 100644 --- a/Casper.Network.SDK.Test/CLValueByteSerializerTest.cs +++ b/Casper.Network.SDK.Test/CLValueByteSerializerTest.cs @@ -372,7 +372,7 @@ public void Tuple3ByteSerializerTest() var clValue = CLValue.Tuple3(CLValue.U32(17), CLValue.U32(127), CLValue.U32(17)); var bytes = serializer.ToBytes(clValue); Assert.AreEqual("0c000000110000007f0000001100000014040404", Hex.ToHexString(bytes)); - + clValue = CLValue.Tuple3(CLValue.U32(127), CLValue.String("ABCDE"), CLValue.U32(127)); bytes = serializer.ToBytes(clValue); Assert.AreEqual("110000007f0000000500000041424344457f00000014040a04", Hex.ToHexString(bytes)); @@ -383,5 +383,219 @@ public void Tuple3ByteSerializerTest() bytes = serializer.ToBytes(CLValue.OptionNone(new CLTuple3TypeInfo(CLType.U32, CLType.String, CLType.U32))); Assert.AreEqual("01000000000d14040a04", Hex.ToHexString(bytes)); } + + // FromBytes tests — each uses the hex produced by the corresponding ToBytes test + + [Test] + public void BoolFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("010000000000")); + Assert.AreEqual(CLType.Bool, clValue.TypeInfo.Type); + Assert.AreEqual("00", Hex.ToHexString(clValue.Bytes)); + + clValue = serializer.FromBytes(Hex.Decode("010000000100")); + Assert.AreEqual(CLType.Bool, clValue.TypeInfo.Type); + Assert.AreEqual("01", Hex.ToHexString(clValue.Bytes)); + + // Option(Bool) + clValue = serializer.FromBytes(Hex.Decode("0200000001000d00")); + Assert.AreEqual(CLType.Option, clValue.TypeInfo.Type); + Assert.AreEqual(CLType.Bool, ((CLOptionTypeInfo)clValue.TypeInfo).OptionType.Type); + + // OptionNone(Bool) + clValue = serializer.FromBytes(Hex.Decode("01000000000d00")); + Assert.AreEqual(CLType.Option, clValue.TypeInfo.Type); + Assert.AreEqual(CLType.Bool, ((CLOptionTypeInfo)clValue.TypeInfo).OptionType.Type); + } + + [Test] + public void I32FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("04000000f6ffffff01")); + Assert.AreEqual(CLType.I32, clValue.TypeInfo.Type); + Assert.AreEqual("f6ffffff", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void I64FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("08000000f0ffffffffffffff02")); + Assert.AreEqual(CLType.I64, clValue.TypeInfo.Type); + Assert.AreEqual("f0ffffffffffffff", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void U8FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("010000007f03")); + Assert.AreEqual(CLType.U8, clValue.TypeInfo.Type); + Assert.AreEqual("7f", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void U32FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("04000000ffffffff04")); + Assert.AreEqual(CLType.U32, clValue.TypeInfo.Type); + Assert.AreEqual("ffffffff", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void U64FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("08000000ffffffffffffffff05")); + Assert.AreEqual(CLType.U64, clValue.TypeInfo.Type); + Assert.AreEqual("ffffffffffffffff", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void U128FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("0900000008ffffffffffffffff06")); + Assert.AreEqual(CLType.U128, clValue.TypeInfo.Type); + Assert.AreEqual("08ffffffffffffffff", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void U256FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("0900000008ffffffffffffffff07")); + Assert.AreEqual(CLType.U256, clValue.TypeInfo.Type); + Assert.AreEqual("08ffffffffffffffff", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void U512FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("0900000008ffffffffffffffff08")); + Assert.AreEqual(CLType.U512, clValue.TypeInfo.Type); + Assert.AreEqual("08ffffffffffffffff", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void UnitFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("0000000009")); + Assert.AreEqual(CLType.Unit, clValue.TypeInfo.Type); + Assert.AreEqual(0, clValue.Bytes.Length); + } + + [Test] + public void StringFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("120000000e00000048656c6c6f2c20436173706572210a")); + Assert.AreEqual(CLType.String, clValue.TypeInfo.Type); + Assert.AreEqual("Hello, Casper!", clValue.ToString()); + } + + [Test] + public void KeyFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode( + "2100000000989ca079a5e446071866331468ab949483162588d57ec13ba6bb051f1e15f8b70b")); + Assert.AreEqual(CLType.Key, clValue.TypeInfo.Type); + Assert.AreEqual(33, clValue.Bytes.Length); + } + + [Test] + public void URefFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode( + "21000000000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f070c")); + Assert.AreEqual(CLType.URef, clValue.TypeInfo.Type); + Assert.AreEqual(33, clValue.Bytes.Length); + } + + [Test] + public void PublicKeyFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode( + "2100000001381b36cd07ad85348607ffe0fa3a2d033ea941d14763358ebeace9c8ad3cb77116")); + Assert.AreEqual(CLType.PublicKey, clValue.TypeInfo.Type); + Assert.AreEqual(33, clValue.Bytes.Length); + } + + [Test] + public void ListFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode( + "1400000004000000010000000200000003000000040000000e04")); + Assert.AreEqual(CLType.List, clValue.TypeInfo.Type); + Assert.AreEqual(CLType.U32, ((CLListTypeInfo)clValue.TypeInfo).ListType.Type); + } + + [Test] + public void ByteArrayFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("0800000001020304050607080f08000000")); + Assert.AreEqual(CLType.ByteArray, clValue.TypeInfo.Type); + Assert.AreEqual(8, ((CLByteArrayTypeInfo)clValue.TypeInfo).Size); + Assert.AreEqual("0102030405060708", Hex.ToHexString(clValue.Bytes)); + } + + [Test] + public void MapFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode( + "1c00000002000000040000006b65793101000000040000006b65793202000000110a04")); + Assert.AreEqual(CLType.Map, clValue.TypeInfo.Type); + var mapType = (CLMapTypeInfo)clValue.TypeInfo; + Assert.AreEqual(CLType.String, mapType.KeyType.Type); + Assert.AreEqual(CLType.U32, mapType.ValueType.Type); + } + + [Test] + public void ResultFromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("0200000001ff10030a")); + Assert.AreEqual(CLType.Result, clValue.TypeInfo.Type); + var resultType = (CLResultTypeInfo)clValue.TypeInfo; + Assert.AreEqual(CLType.U8, resultType.Ok.Type); + Assert.AreEqual(CLType.String, resultType.Err.Type); + } + + [Test] + public void Tuple1FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("04000000110000001204")); + Assert.AreEqual(CLType.Tuple1, clValue.TypeInfo.Type); + Assert.AreEqual(CLType.U32, ((CLTuple1TypeInfo)clValue.TypeInfo).Type0.Type); + } + + [Test] + public void Tuple2FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("08000000110000007f000000130404")); + Assert.AreEqual(CLType.Tuple2, clValue.TypeInfo.Type); + var tupleType = (CLTuple2TypeInfo)clValue.TypeInfo; + Assert.AreEqual(CLType.U32, tupleType.Type0.Type); + Assert.AreEqual(CLType.U32, tupleType.Type1.Type); + } + + [Test] + public void Tuple3FromBytesTest() + { + var clValue = serializer.FromBytes(Hex.Decode("0c000000110000007f0000001100000014040404")); + Assert.AreEqual(CLType.Tuple3, clValue.TypeInfo.Type); + var tupleType = (CLTuple3TypeInfo)clValue.TypeInfo; + Assert.AreEqual(CLType.U32, tupleType.Type0.Type); + Assert.AreEqual(CLType.U32, tupleType.Type1.Type); + Assert.AreEqual(CLType.U32, tupleType.Type2.Type); + } + + [Test] + public void RoundTripFromBytesTest() + { + // Verify that ToBytes(FromBytes(ToBytes(x))) == ToBytes(x) for a complex nested type + var original = CLValue.Option(CLValue.Map(new Dictionary + { + { CLValue.String("key1"), CLValue.U32(1) }, + { CLValue.String("key2"), CLValue.U32(2) } + })); + var encoded = serializer.ToBytes(original); + var decoded = serializer.FromBytes(encoded); + var reEncoded = serializer.ToBytes(decoded); + Assert.AreEqual(Hex.ToHexString(encoded), Hex.ToHexString(reEncoded)); + } } } \ No newline at end of file diff --git a/Casper.Network.SDK.Test/CLValueDictionaryTest.cs b/Casper.Network.SDK.Test/CLValueDictionaryTest.cs new file mode 100644 index 0000000..d0a3a3c --- /dev/null +++ b/Casper.Network.SDK.Test/CLValueDictionaryTest.cs @@ -0,0 +1,40 @@ +using Casper.Network.SDK.Types; +using NUnit.Framework; +using Org.BouncyCastle.Utilities.Encoders; + +namespace NetCasperTest +{ + [TestFixture] + public class CLValueDictionaryTest + { + private const string TestHex = + "eb000000e70000000a0000006576656e745f4d696e74003ead5986ccbcb5b632489b1b60577f1aefd833c0e1ee7ebd3d2a31ff74cb7f320400000031393735ac0000007b226e616d65223a22435350522e6c6976652b2032303234222c226465736372697074696f6e223a224772616e74732061636365737320746f207072656d69756d20666561747572657320696e20435350522e6c697665222c22696d616765223a2268747470733a2f2f6361737065722d6173736574732e73332e616d617a6f6e6177732e636f6d2f637370726c697665706c75732f323032342e706e67222c2279656172223a323032347d0e0320000000cab34dbd7203ecfe5b215545a3854c4b146af40bd0b556019c26c2ac4af4c87c0400000031393735"; + + [Test] + public void Parse_ReturnsCorrectValue() + { + var result = CLValueDictionary.Parse(Hex.Decode(TestHex)); + + Assert.IsNotNull(result.Value); + } + + [Test] + public void Parse_ReturnsCorrectSeed() + { + var result = CLValueDictionary.Parse(Hex.Decode(TestHex)); + + Assert.AreEqual( + "uref-cab34dbd7203ecfe5b215545a3854c4b146af40bd0b556019c26c2ac4af4c87c-000", + result.Seed.ToString()); + } + + [Test] + public void Parse_ReturnsCorrectItemKey() + { + var result = CLValueDictionary.Parse(Hex.Decode(TestHex)); + + Assert.AreEqual("1975", result.ItemKey); + } + } +} + diff --git a/Casper.Network.SDK.Test/NamedArgJsonSerializerTest.cs b/Casper.Network.SDK.Test/NamedArgJsonSerializerTest.cs index 72b9e40..4d3d258 100644 --- a/Casper.Network.SDK.Test/NamedArgJsonSerializerTest.cs +++ b/Casper.Network.SDK.Test/NamedArgJsonSerializerTest.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; using System.Linq; +using System.Numerics; using System.Text.Json; +using Casper.Network.SDK.ByteSerializers; using Casper.Network.SDK.Converters; using Casper.Network.SDK.Types; using NUnit.Framework; @@ -9,6 +12,13 @@ namespace NetCasperTest { public class NamedArgJsonSerializerTest { + private NamedArgByteSerializer _byteSerializer; + + [SetUp] + public void SetUp() + { + _byteSerializer = new NamedArgByteSerializer(); + } private string NAMEDARG_TARGET = @"[ ""target"", { @@ -58,5 +68,151 @@ public void SerializeNamedArgTarget() var clValue2 = JsonSerializer.Deserialize(json.EnumerateArray().Last().GetRawText()); Assert.IsNotNull(clValue2); } + + [Test] + public void FromBytes_Bool() + { + // "flag" + CLValue.Bool(true) => name(u32+bytes) + clvalue(u32 len + data + type tag) + var original = new NamedArg("flag", CLValue.Bool(true)); + var encoded = _byteSerializer.ToBytes(original); + var result = _byteSerializer.FromBytes(encoded); + + Assert.AreEqual("flag", result.Name); + Assert.AreEqual(CLType.Bool, result.Value.TypeInfo.Type); + Assert.AreEqual("01", Hex.ToHexString(result.Value.Bytes)); + } + + [Test] + public void FromBytes_U32() + { + var original = new NamedArg("amount", CLValue.U32(uint.MaxValue)); + var encoded = _byteSerializer.ToBytes(original); + var result = _byteSerializer.FromBytes(encoded); + + Assert.AreEqual("amount", result.Name); + Assert.AreEqual(CLType.U32, result.Value.TypeInfo.Type); + Assert.AreEqual("ffffffff", Hex.ToHexString(result.Value.Bytes)); + } + + [Test] + public void FromBytes_U512() + { + var original = new NamedArg("payment", CLValue.U512(2_500_000_000UL)); + var encoded = _byteSerializer.ToBytes(original); + var result = _byteSerializer.FromBytes(encoded); + + Assert.AreEqual("payment", result.Name); + Assert.AreEqual(CLType.U512, result.Value.TypeInfo.Type); + } + + [Test] + public void FromBytes_String() + { + var original = new NamedArg("greeting", CLValue.String("Hello, Casper!")); + var encoded = _byteSerializer.ToBytes(original); + var result = _byteSerializer.FromBytes(encoded); + + Assert.AreEqual("greeting", result.Name); + Assert.AreEqual(CLType.String, result.Value.TypeInfo.Type); + Assert.AreEqual("Hello, Casper!", result.Value.ToString()); + } + + [Test] + public void FromBytes_ByteArray() + { + var original = new NamedArg("target", + CLValue.ByteArray("989ca079a5e446071866331468ab949483162588d57ec13ba6bb051f1e15f8b7")); + var encoded = _byteSerializer.ToBytes(original); + var result = _byteSerializer.FromBytes(encoded); + + Assert.AreEqual("target", result.Name); + Assert.AreEqual(CLType.ByteArray, result.Value.TypeInfo.Type); + Assert.AreEqual(32, ((CLByteArrayTypeInfo)result.Value.TypeInfo).Size); + Assert.AreEqual("989ca079a5e446071866331468ab949483162588d57ec13ba6bb051f1e15f8b7", + Hex.ToHexString(result.Value.Bytes)); + } + + [Test] + public void FromBytes_URef() + { + var uref = "uref-000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f-007"; + var original = new NamedArg("purse", CLValue.URef(uref)); + var encoded = _byteSerializer.ToBytes(original); + var result = _byteSerializer.FromBytes(encoded); + + Assert.AreEqual("purse", result.Name); + Assert.AreEqual(CLType.URef, result.Value.TypeInfo.Type); + Assert.AreEqual(33, result.Value.Bytes.Length); + } + + [Test] + public void FromBytes_OptionString() + { + var original = new NamedArg("maybe", CLValue.Option(CLValue.String("present"))); + var encoded = _byteSerializer.ToBytes(original); + var result = _byteSerializer.FromBytes(encoded); + + Assert.AreEqual("maybe", result.Name); + Assert.AreEqual(CLType.Option, result.Value.TypeInfo.Type); + Assert.AreEqual(CLType.String, ((CLOptionTypeInfo)result.Value.TypeInfo).OptionType.Type); + } + + [Test] + public void FromBytes_Map() + { + var original = new NamedArg("data", CLValue.Map(new Dictionary + { + { CLValue.String("key1"), CLValue.U32(1) }, + { CLValue.String("key2"), CLValue.U32(2) } + })); + var encoded = _byteSerializer.ToBytes(original); + var result = _byteSerializer.FromBytes(encoded); + + Assert.AreEqual("data", result.Name); + Assert.AreEqual(CLType.Map, result.Value.TypeInfo.Type); + var mapType = (CLMapTypeInfo)result.Value.TypeInfo; + Assert.AreEqual(CLType.String, mapType.KeyType.Type); + Assert.AreEqual(CLType.U32, mapType.ValueType.Type); + } + + [Test] + public void FromBytes_RoundTrip() + { + // Verify ToBytes(FromBytes(ToBytes(x))) == ToBytes(x) + var original = new NamedArg("value", CLValue.U64(ulong.MaxValue)); + var encoded = _byteSerializer.ToBytes(original); + var decoded = _byteSerializer.FromBytes(encoded); + var reEncoded = _byteSerializer.ToBytes(decoded); + + Assert.AreEqual(Hex.ToHexString(encoded), Hex.ToHexString(reEncoded)); + } + + [Test] + public void FromBytes_List() + { + var bytes = + "050000000a000000616d6f756e745f6f75740600000005ff54fceb06070d000000616d6f756e745f696e5f6d6178060000000500c846105e07040000007061746867000000030000000130f66fc8f60ee99e7a8a05c3a308ee623c229247c0cc7ac8dc76621157397a93018df5d26790e18cf0404502c62ce5dc9025800ad6975c97466e20506c39c505b601d65a7e04b687bd873af5819de67d563ff347560dfef16ad2c1065cac3e115b960e0b02000000746f21000000006dbf32d39ecc2c5936cf2ac132562e27630a9721156d249837f05348616709dc0b08000000646561646c696e6508000000ffffffffffffffff05"; + + var namedArgs = NamedArg.ListFromBytes(Hex.Decode(bytes)); + Assert.AreEqual(5, namedArgs.Count); + Assert.AreEqual("amount_out", namedArgs[0].Name); + Assert.AreEqual(CLType.U256, namedArgs[0].Value.TypeInfo.Type); + Assert.AreEqual(BigInteger.Parse("29728986367"), namedArgs[0].Value.ToBigInteger()); + Assert.AreEqual("amount_in_max", namedArgs[1].Name); + Assert.AreEqual(CLType.U256, namedArgs[1].Value.TypeInfo.Type); + Assert.AreEqual(BigInteger.Parse("404000000000"), namedArgs[1].Value.ToBigInteger()); + Assert.AreEqual("path", namedArgs[2].Name); + Assert.AreEqual(CLType.List, namedArgs[2].Value.TypeInfo.Type); + var list = namedArgs[2].Value.ToList(); + Assert.AreEqual(3, list.Count); + Assert.AreEqual("d65a7e04b687bd873af5819de67d563ff347560dfef16ad2c1065cac3e115b96", list[2].ToHexString()); + Assert.AreEqual("to", namedArgs[3].Name); + Assert.AreEqual(CLType.Key, namedArgs[3].Value.TypeInfo.Type); + Assert.AreEqual("account-hash-6dbf32d39ecc2c5936cf2ac132562e27630a9721156d249837f05348616709dc", + namedArgs[3].Value.ToString()); + Assert.AreEqual("deadline", namedArgs[4].Name); + Assert.AreEqual(CLType.U64, namedArgs[4].Value.TypeInfo.Type); + Assert.AreEqual(ulong.MaxValue, namedArgs[4].Value.ToUInt64()); + } } } \ No newline at end of file diff --git a/Casper.Network.SDK/ByteSerializers/CLValueByteSerializer.cs b/Casper.Network.SDK/ByteSerializers/CLValueByteSerializer.cs index 1262178..d4fc8eb 100644 --- a/Casper.Network.SDK/ByteSerializers/CLValueByteSerializer.cs +++ b/Casper.Network.SDK/ByteSerializers/CLValueByteSerializer.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Casper.Network.SDK.Types; +using Casper.Network.SDK.Utils; using Org.BouncyCastle.Asn1.X509.Qualified; namespace Casper.Network.SDK.ByteSerializers @@ -16,13 +17,51 @@ public byte[] ToBytes(CLValue source) // serialize data // WriteBytes(ms, source.Bytes); - // serialize type and inner types (if any) recursively + // serialize type and inner types (if any) recursively // CLTypeToBytes(ms, source.TypeInfo); return ms.ToArray(); } + public CLValue FromBytes(byte[] bytes) + { + using var ms = new MemoryStream(bytes); + using var reader = new BinaryReader(ms); + + return FromReader(reader); + } + + public CLValue FromReader(BinaryReader reader) + { + // read data length and data bytes + var dataLength = reader.ReadCLU32(); + var dataBytes = reader.ReadBytes((int)dataLength); + + // read type info recursively + var typeInfo = CLTypeFromBytes(reader); + + return new CLValue(dataBytes, typeInfo); + } + + private CLTypeInfo CLTypeFromBytes(BinaryReader reader) + { + var tag = (CLType)reader.ReadByte(); + + return tag switch + { + CLType.Option => new CLOptionTypeInfo(CLTypeFromBytes(reader)), + CLType.List => new CLListTypeInfo(CLTypeFromBytes(reader)), + CLType.ByteArray => new CLByteArrayTypeInfo(reader.ReadCLI32()), + CLType.Result => new CLResultTypeInfo(CLTypeFromBytes(reader), CLTypeFromBytes(reader)), + CLType.Map => new CLMapTypeInfo(CLTypeFromBytes(reader), CLTypeFromBytes(reader)), + CLType.Tuple1 => new CLTuple1TypeInfo(CLTypeFromBytes(reader)), + CLType.Tuple2 => new CLTuple2TypeInfo(CLTypeFromBytes(reader), CLTypeFromBytes(reader)), + CLType.Tuple3 => new CLTuple3TypeInfo(CLTypeFromBytes(reader), CLTypeFromBytes(reader), CLTypeFromBytes(reader)), + _ => new CLTypeInfo(tag) + }; + } + private void CLTypeToBytes(MemoryStream ms, CLTypeInfo innerType) { WriteByte(ms, (byte)innerType.Type); diff --git a/Casper.Network.SDK/ByteSerializers/NamedArgByteSerializer.cs b/Casper.Network.SDK/ByteSerializers/NamedArgByteSerializer.cs index 4c9dc09..8a2f869 100644 --- a/Casper.Network.SDK/ByteSerializers/NamedArgByteSerializer.cs +++ b/Casper.Network.SDK/ByteSerializers/NamedArgByteSerializer.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Casper.Network.SDK.Types; +using Casper.Network.SDK.Utils; namespace Casper.Network.SDK.ByteSerializers { @@ -16,8 +17,23 @@ public byte[] ToBytes(NamedArg source) var valueSerializer = new CLValueByteSerializer(); WriteBytes(ms, valueSerializer.ToBytes(source.Value)); - + return ms.ToArray(); } + + public NamedArg FromBytes(byte[] bytes) + { + using var ms = new MemoryStream(bytes); + using var reader = new BinaryReader(ms); + + return FromReader(reader); + } + + public NamedArg FromReader(BinaryReader reader) + { + var name = reader.ReadCLString(); + var clValue = new CLValueByteSerializer().FromReader(reader); + return new NamedArg(name, clValue); + } } } \ No newline at end of file diff --git a/Casper.Network.SDK/Types/CLValueDictionary.cs b/Casper.Network.SDK/Types/CLValueDictionary.cs new file mode 100644 index 0000000..0773fba --- /dev/null +++ b/Casper.Network.SDK/Types/CLValueDictionary.cs @@ -0,0 +1,68 @@ +using System.IO; +using Casper.Network.SDK.ByteSerializers; +using Casper.Network.SDK.Utils; + +namespace Casper.Network.SDK.Types +{ + /// + /// Represents a parsed CLValue dictionary entry containing a raw value, a seed URef, and an item key string. + /// + public class CLValueDictionary + { + /// + /// The raw value bytes of the dictionary entry. + /// + public byte[] Value { get; } + + /// + /// The seed URef associated with the dictionary. + /// + public URef Seed { get; } + + /// + /// The string key identifying the item in the dictionary. + /// + public string ItemKey { get; } + + private CLValueDictionary(byte[] value, URef seed, string itemKey) + { + Value = value; + Seed = seed; + ItemKey = itemKey; + } + + /// + /// Parses a byte array into a instance. + /// + /// The raw bytes to parse. + /// A new instance. + public static CLValueDictionary Parse(byte[] bytes) + { + using var ms = new MemoryStream(bytes); + using var reader = new BinaryReader(ms); + + // (2) Read uint32 length of the value bytes + var valueLength = reader.ReadCLU32(); + + // (3) Read the value bytes + var serializer = new CLValueByteSerializer(); + var value = serializer.FromReader(reader); + + // (4) Read the URef key tag byte (KeyIdentifier.URef = 0x0e) + // reader.ReadByte(); + + // (5) Read the access rights byte, then the seed length, then the seed bytes. + // Create a URef with the seed bytes and permissions '0'. + // var accessRights = (AccessRights)reader.ReadByte(); + var seedLength = reader.ReadCLU32(); + var seedBytes = reader.ReadBytes((int)seedLength); + var seed = new URef(seedBytes, AccessRights.NONE); + + // Read a CLValue string (u32 length prefix + UTF-8 bytes) + var itemKey = reader.ReadCLString(); + + return new CLValueDictionary(value.Bytes, seed, itemKey); + } + } +} + diff --git a/Casper.Network.SDK/Types/NamedArg.cs b/Casper.Network.SDK/Types/NamedArg.cs index 1324e4c..d5b90b2 100644 --- a/Casper.Network.SDK/Types/NamedArg.cs +++ b/Casper.Network.SDK/Types/NamedArg.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; using Casper.Network.SDK.Converters; namespace Casper.Network.SDK.Types @@ -12,7 +15,7 @@ namespace Casper.Network.SDK.Types public class NamedArg { public string Name { get; } - + public CLValue Value { get; } /// @@ -51,28 +54,28 @@ public NamedArg(string name, long value) : this(name, CLValue.I64(value)) public NamedArg(string name, byte value) : this(name, CLValue.U8(value)) { } - + /// /// Creates a NamedArg object with an UInt32 value. /// public NamedArg(string name, uint value) : this(name, CLValue.U32(value)) { } - + /// /// Creates a NamedArg object with an UInt64 value. /// public NamedArg(string name, ulong value) : this(name, CLValue.U64(value)) { } - + /// /// Creates a NamedArg object with a BigInteger value. /// public NamedArg(string name, BigInteger value) : this(name, CLValue.U512(value)) { } - + /// /// Creates a NamedArg object with a string value. /// @@ -93,22 +96,22 @@ public NamedArg(string name, URef value) : this(name, CLValue.URef(value)) public NamedArg(string name, byte[] value) : this(name, CLValue.ByteArray(value)) { } - + /// /// Creates a NamedArg object with a PublicKey value. /// public NamedArg(string name, PublicKey value) : this(name, CLValue.PublicKey(value)) { } - + /// /// Creates a NamedArg object with a GlobalStateKey value. /// public NamedArg(string name, GlobalStateKey value) : this(name, CLValue.Key(value)) { } - - public class NamedArgConverter: JsonConverter + + public class NamedArgConverter : JsonConverter { public override NamedArg Read( ref Utf8JsonReader reader, @@ -119,7 +122,7 @@ public override NamedArg Read( var name = reader.GetString(); reader.Read(); CLValue clValue = null; - if(reader.TokenType == JsonTokenType.StartObject) + if (reader.TokenType == JsonTokenType.StartObject) clValue = JsonSerializer.Deserialize(ref reader, options); reader.Read(); // end object return new NamedArg(name, clValue); @@ -136,5 +139,30 @@ public override void Write( writer.WriteEndArray(); } } + + public static IList ListFromBytes(byte[] bytes) + { + //create a memory stream from the bytes + using var ms = new MemoryStream(bytes); + using var reader = new BinaryReader(ms); + + return ListFromReader(reader); + } + + public static IList ListFromReader(BinaryReader reader) + { + // read the number of named args + var numNamedArgs = reader.ReadInt32(); + + var namedArgs = new List(numNamedArgs); + + for (int i = 0; i < numNamedArgs; i++) + { + var namedArg = new NamedArgByteSerializer().FromReader(reader); + namedArgs.Add(namedArg); + } + + return namedArgs; + } } } \ No newline at end of file