From c24a32aba8f14def385cbd1319484db89354aa68 Mon Sep 17 00:00:00 2001 From: hugo Date: Sat, 3 Jul 2021 00:03:17 +0100 Subject: [PATCH] Enhancements and utilities (#138) * Added extension methods to facilitate program data encoding and decoding and abstract class for bit masks. --- README.md | 1 - src/Solnet.Programs/Abstract/ByteFlag.cs | 54 +++++ src/Solnet.Programs/Abstract/Flag.cs | 22 ++ src/Solnet.Programs/MemoProgram.cs | 6 +- src/Solnet.Programs/NameServiceProgram.cs | 50 +++-- src/Solnet.Programs/SharedMemoryProgram.cs | 105 ++++----- src/Solnet.Programs/SystemProgramData.cs | 104 +++++---- src/Solnet.Programs/TokenProgram.cs | 6 +- src/Solnet.Programs/TokenProgramData.cs | 42 ++-- .../Utilities/Deserialization.cs | 176 +++++++++++++++ .../Utilities/Serialization.cs | 190 ++++++++++++++++ src/Solnet.Programs/Utils.cs | 40 ---- .../Abstract/ByteFlagTest.cs | 129 +++++++++++ .../Solnet.Programs.Test/SystemProgramTest.cs | 4 +- .../Utilities/DeserializationUtilitiesTest.cs | 212 ++++++++++++++++++ .../Utilities/SerializationUtilitiesTest.cs | 202 +++++++++++++++++ 16 files changed, 1160 insertions(+), 183 deletions(-) create mode 100644 src/Solnet.Programs/Abstract/ByteFlag.cs create mode 100644 src/Solnet.Programs/Abstract/Flag.cs create mode 100644 src/Solnet.Programs/Utilities/Deserialization.cs create mode 100644 src/Solnet.Programs/Utilities/Serialization.cs delete mode 100644 src/Solnet.Programs/Utils.cs create mode 100644 test/Solnet.Programs.Test/Abstract/ByteFlagTest.cs create mode 100644 test/Solnet.Programs.Test/Utilities/DeserializationUtilitiesTest.cs create mode 100644 test/Solnet.Programs.Test/Utilities/SerializationUtilitiesTest.cs diff --git a/README.md b/README.md index bf72ac91..19c9ccb6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ Code License -
Follow on Twitter diff --git a/src/Solnet.Programs/Abstract/ByteFlag.cs b/src/Solnet.Programs/Abstract/ByteFlag.cs new file mode 100644 index 00000000..50579baf --- /dev/null +++ b/src/Solnet.Programs/Abstract/ByteFlag.cs @@ -0,0 +1,54 @@ +namespace Solnet.Programs.Abstract +{ + /// + /// Represents a flag using a byte for masking. + /// + public class ByteFlag : Flag + { + /// + /// Check if the 1st bit is set. + /// + public bool Bit0 => (Value & 0x01) != 0; + + /// + /// Check if the 2nd bit is set. + /// + public bool Bit1 => (Value & 0x02) != 0; + + /// + /// Check if the 3rc bit is set. + /// + public bool Bit2 => (Value & 0x04) != 0; + + /// + /// Check if the 4th bit is set. + /// + public bool Bit3 => (Value & 0x08) != 0; + + /// + /// Check if the 5th bit is set. + /// + public bool Bit4 => (Value & 0x10) != 0; + + /// + /// Check if the 6th bit is set. + /// + public bool Bit5 => (Value & 0x20) != 0; + + /// + /// Check if the 7th bit is set. + /// + public bool Bit6 => (Value & 0x40) != 0; + + /// + /// Check if the 8th bit is set. + /// + public bool Bit7 => (Value & 0x80) != 0; + + /// + /// Initialize the flags with the given byte. + /// + /// The byte to use. + public ByteFlag(byte mask) : base(mask) { } + } +} \ No newline at end of file diff --git a/src/Solnet.Programs/Abstract/Flag.cs b/src/Solnet.Programs/Abstract/Flag.cs new file mode 100644 index 00000000..66dcf515 --- /dev/null +++ b/src/Solnet.Programs/Abstract/Flag.cs @@ -0,0 +1,22 @@ +namespace Solnet.Programs.Abstract +{ + /// + /// Represents bitmask flags for various types of accounts within Solana Programs. + /// + public abstract class Flag + { + /// + /// The mask for the account flags. + /// + public T Value { get; } + + /// + /// Initialize the flags with the given mask. + /// + /// The mask to use. + protected Flag(T mask) + { + Value = mask; + } + } +} \ No newline at end of file diff --git a/src/Solnet.Programs/MemoProgram.cs b/src/Solnet.Programs/MemoProgram.cs index 9fc15ef7..076d8187 100644 --- a/src/Solnet.Programs/MemoProgram.cs +++ b/src/Solnet.Programs/MemoProgram.cs @@ -27,11 +27,11 @@ public static class MemoProgram /// The which includes the memo data. public static TransactionInstruction NewMemo(Account account, string memo) { - var keys = new List + List keys = new () { - new (account, false) + new AccountMeta(account, false) }; - var memoBytes = Encoding.UTF8.GetBytes(memo); + byte[] memoBytes = Encoding.UTF8.GetBytes(memo); return new TransactionInstruction { diff --git a/src/Solnet.Programs/NameServiceProgram.cs b/src/Solnet.Programs/NameServiceProgram.cs index 848d01e1..2fbe5452 100644 --- a/src/Solnet.Programs/NameServiceProgram.cs +++ b/src/Solnet.Programs/NameServiceProgram.cs @@ -1,3 +1,4 @@ +using Solnet.Programs.Utilities; using Solnet.Rpc.Models; using Solnet.Rpc.Utilities; using Solnet.Wallet; @@ -45,13 +46,11 @@ public static class NameServiceProgram /// The transaction instruction. /// Thrown when it was not possible to derive a program address for the account. public static TransactionInstruction CreateNameRegistry( - PublicKey name, Account payer, PublicKey nameOwner, ulong lamports, int space, Account nameClass = null, + PublicKey name, Account payer, PublicKey nameOwner, ulong lamports, uint space, Account nameClass = null, Account parentNameOwner = null, PublicKey parentName = null) { byte[] hashedName = ComputeHashedName(name.Key); - Console.WriteLine("Hashed Name: " + Convert.ToHexString(hashedName).ToLowerInvariant()); PublicKey nameAccountKey = DeriveNameAccountKey(hashedName, nameClass?.PublicKey, parentName); - Console.WriteLine("Name Account Key: " + nameAccountKey.Key); if (nameAccountKey == null) throw new Exception("could not derive an address for the name account"); return CreateNameRegistryInstruction( nameAccountKey, nameOwner, payer, hashedName, lamports, space, nameClass, parentNameOwner, parentName); @@ -65,7 +64,6 @@ public static TransactionInstruction CreateNameRegistry( public static byte[] ComputeHashedName(string name) { string prefixedName = HashPrefix + name; - Console.WriteLine(prefixedName); byte[] fullNameBytes = Encoding.UTF8.GetBytes(prefixedName); using SHA256Managed sha = new(); return sha.ComputeHash(fullNameBytes, 0, fullNameBytes.Length); @@ -78,7 +76,7 @@ public static byte[] ComputeHashedName(string name) /// The account of the name class. /// The public key of the parent name. /// The program derived address for the name. - public static PublicKey DeriveNameAccountKey(byte[] hashedName, PublicKey nameClass = null, PublicKey parentName = null) + public static PublicKey DeriveNameAccountKey(ReadOnlySpan hashedName, PublicKey nameClass = null, PublicKey parentName = null) { byte[] nameClassKey = new byte[32]; byte[] parentNameKeyBytes = new byte[32]; @@ -89,7 +87,7 @@ public static PublicKey DeriveNameAccountKey(byte[] hashedName, PublicKey nameCl try { (byte[] nameAccountPublicKey, _) = AddressExtensions.FindProgramAddress( - new List {hashedName, nameClassKey, parentNameKeyBytes}, ProgramIdKey.KeyBytes); + new List {hashedName.ToArray(), nameClassKey, parentNameKeyBytes}, ProgramIdKey.KeyBytes); return new PublicKey(nameAccountPublicKey); } catch (Exception) @@ -113,7 +111,7 @@ public static PublicKey DeriveNameAccountKey(byte[] hashedName, PublicKey nameCl /// The account of the parent name owner. /// The transaction instruction. private static TransactionInstruction CreateNameRegistryInstruction( - PublicKey nameKey, PublicKey nameOwner, Account payer, byte[] hashedName, ulong lamports, int space, + PublicKey nameKey, PublicKey nameOwner, Account payer, ReadOnlySpan hashedName, ulong lamports, uint space, Account nameClass = null, Account parentNameOwner = null, PublicKey parentName = null) { List keys = new() @@ -151,7 +149,7 @@ private static TransactionInstruction CreateNameRegistryInstruction( /// The account of the name class. /// The transaction instruction. public static TransactionInstruction UpdateNameRegistry( - PublicKey nameKey, int offset, byte[] data, Account nameOwner = null, Account nameClass = null) + PublicKey nameKey, uint offset, ReadOnlySpan data, Account nameOwner = null, Account nameClass = null) { List keys = new() {new AccountMeta(nameKey, true)}; @@ -194,7 +192,7 @@ public static TransactionInstruction TransferNameRegistry( return new TransactionInstruction { - Keys = keys, ProgramId = ProgramIdKey.KeyBytes, Data = EncodeTransferNameRegistryData(newOwner.KeyBytes) + Keys = keys, ProgramId = ProgramIdKey.KeyBytes, Data = EncodeTransferNameRegistryData(newOwner) }; } @@ -228,15 +226,15 @@ public static TransactionInstruction DeleteNameRegistry( /// The number of lamports for rent exemption. /// The space for the account. /// The transaction instruction data. - private static byte[] EncodeCreateNameRegistryData(byte[] hashedName, ulong lamports, int space) + private static byte[] EncodeCreateNameRegistryData(ReadOnlySpan hashedName, ulong lamports, uint space) { byte[] methodBuffer = new byte[49]; - methodBuffer[0] = (byte)NameServiceInstructions.Create; - Utils.Uint32ToByteArrayLe((ulong) hashedName.Length, methodBuffer, 1); - Array.Copy(hashedName, 0, methodBuffer, 5, hashedName.Length); - Utils.Int64ToByteArrayLe(lamports, methodBuffer, 37); - Utils.Uint32ToByteArrayLe((ulong) space, methodBuffer, 45); + methodBuffer.WriteU8((byte)NameServiceInstructions.Create, 0); + methodBuffer.WriteU32((uint) hashedName.Length, 1); + methodBuffer.WriteSpan(hashedName, 5); + methodBuffer.WriteU64(lamports, 37); + methodBuffer.WriteU32(space, 45); return methodBuffer; } @@ -247,13 +245,13 @@ private static byte[] EncodeCreateNameRegistryData(byte[] hashedName, ulong lamp /// The offset at which to update the data. /// The data to insert. /// The transaction instruction data. - private static byte[] EncodeUpdateNameRegistryData(int offset, byte[] data) + private static byte[] EncodeUpdateNameRegistryData(uint offset, ReadOnlySpan data) { byte[] methodBuffer = new byte[data.Length + 5]; - methodBuffer[0] = (byte)NameServiceInstructions.Update; - Utils.Uint32ToByteArrayLe((ulong) offset, methodBuffer, 1); - Array.Copy(data, 0, methodBuffer, 5, data.Length); + methodBuffer.WriteU8((byte)NameServiceInstructions.Update, 0); + methodBuffer.WriteU32(offset, 1); + methodBuffer.WriteSpan(data, 5); return methodBuffer; } @@ -261,13 +259,15 @@ private static byte[] EncodeUpdateNameRegistryData(int offset, byte[] data) /// /// Encode the instruction data to be used with the instruction. /// - /// The public key of the account to transfer ownership to. + /// The public key of the account to transfer ownership to. /// The transaction instruction data. - private static byte[] EncodeTransferNameRegistryData(byte[] publicKey) + private static byte[] EncodeTransferNameRegistryData(PublicKey newOwner) { byte[] methodBuffer = new byte[33]; - methodBuffer[0] = (byte)NameServiceInstructions.Transfer; - Array.Copy(publicKey, 0, methodBuffer, 1, 32); + + methodBuffer.WriteU8((byte)NameServiceInstructions.Transfer, 0); + methodBuffer.WritePubKey(newOwner, 1); + return methodBuffer; } @@ -278,7 +278,9 @@ private static byte[] EncodeTransferNameRegistryData(byte[] publicKey) private static byte[] EncodeDeleteNameRegistryData() { byte[] methodBuffer = new byte[1]; - methodBuffer[0] = (byte)NameServiceInstructions.Delete; + + methodBuffer.WriteU8((byte)NameServiceInstructions.Delete, 0); + return methodBuffer; } } diff --git a/src/Solnet.Programs/SharedMemoryProgram.cs b/src/Solnet.Programs/SharedMemoryProgram.cs index b7d161d8..6c36a69c 100644 --- a/src/Solnet.Programs/SharedMemoryProgram.cs +++ b/src/Solnet.Programs/SharedMemoryProgram.cs @@ -1,53 +1,54 @@ -using Solnet.Rpc.Models; -using Solnet.Wallet; -using Solnet.Wallet.Utilities; -using System.Collections.Generic; -using System.Text; - -namespace Solnet.Programs -{ - /// - /// Helper class for the Shared Memory Program. - /// - /// Used to write data to a given account data. - /// Note: this programm, as of writting this, was inactive in some clusters. - /// - /// - public static class SharedMemoryProgram - { - /// - /// The address of the Shared Memory Program. - /// - public static readonly PublicKey ProgramId = new("shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL"); - - /// - /// Creates an instruction used to interact with the Shared memory program. - /// This instruction writes data to a given program starting at a specific offset. - /// - /// The account where the data is to be written. - /// The data to be written. - /// The offset of the account data to write to. - /// The encoded that interacts with the shared memory program.. - public static TransactionInstruction Write(PublicKey dest, byte[] payload, ulong offset) - { - var keys = new List - { - new (dest, true) - }; - - var transactionData = new byte[payload.Length + 8]; - - Utils.Int64ToByteArrayLe(offset, transactionData, 0); - - payload.CopyTo(transactionData, 8); - - return new TransactionInstruction - { - ProgramId = ProgramId.KeyBytes, - Keys = keys, - Data = transactionData - }; - } - - } +using Solnet.Programs.Utilities; +using Solnet.Rpc.Models; +using Solnet.Wallet; +using Solnet.Wallet.Utilities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Solnet.Programs +{ + /// + /// Helper class for the Shared Memory Program. + /// + /// Used to write data to a given account data. + /// Note: this program, as of writing this, was inactive in some clusters. + /// + /// + public static class SharedMemoryProgram + { + /// + /// The address of the Shared Memory Program. + /// + public static readonly PublicKey ProgramId = new("shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL"); + + /// + /// Creates an instruction used to interact with the Shared memory program. + /// This instruction writes data to a given program starting at a specific offset. + /// + /// The account where the data is to be written. + /// The data to be written. + /// The offset of the account data to write to. + /// The encoded that interacts with the shared memory program.. + public static TransactionInstruction Write(PublicKey dest, ReadOnlySpan payload, ulong offset) + { + List keys = new () + { + new AccountMeta(dest, true) + }; + + byte[] transactionData = new byte[payload.Length + 8]; + + transactionData.WriteU64(offset, 0); + transactionData.WriteSpan(payload, 8); + + return new TransactionInstruction + { + ProgramId = ProgramId.KeyBytes, + Keys = keys, + Data = transactionData + }; + } + + } } \ No newline at end of file diff --git a/src/Solnet.Programs/SystemProgramData.cs b/src/Solnet.Programs/SystemProgramData.cs index 9ee28b01..22adbf31 100644 --- a/src/Solnet.Programs/SystemProgramData.cs +++ b/src/Solnet.Programs/SystemProgramData.cs @@ -1,5 +1,4 @@ -// unset - +using Solnet.Programs.Utilities; using Solnet.Wallet; using System; using System.Text; @@ -21,10 +20,12 @@ internal static class SystemProgramData internal static byte[] EncodeCreateAccountData(PublicKey owner, ulong lamports, ulong space) { byte[] data = new byte[52]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.CreateAccount, data, 0); - Utils.Int64ToByteArrayLe(lamports, data, 4); - Utils.Int64ToByteArrayLe(space, data, 12); - Array.Copy(owner.KeyBytes, 0, data, 20, 32); + + data.WriteU32((uint) SystemProgramInstructions.CreateAccount, 0); + data.WriteU64(lamports, 4); + data.WriteU64(space, 12); + data.WritePubKey(owner, 20); + return data; } @@ -36,8 +37,10 @@ internal static byte[] EncodeCreateAccountData(PublicKey owner, ulong lamports, internal static byte[] EncodeAssignData(PublicKey programId) { byte[] data = new byte[36]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.Assign, data, 0); - Array.Copy(programId.KeyBytes, 0, data, 4, 32); + + data.WriteU32((uint) SystemProgramInstructions.Assign, 0); + data.WritePubKey(programId, 4); + return data; } @@ -49,8 +52,10 @@ internal static byte[] EncodeAssignData(PublicKey programId) internal static byte[] EncodeTransferData(ulong lamports) { byte[] data = new byte[12]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.Transfer, data, 0); - Utils.Int64ToByteArrayLe(lamports, data, 4); + + data.WriteU32((uint) SystemProgramInstructions.Transfer, 0); + data.WriteU64(lamports, 4); + return data; } @@ -68,12 +73,14 @@ internal static byte[] EncodeCreateAccountWithSeedData( { byte[] encodedSeed = EncodeRustString(seed); byte[] data = new byte[84 + encodedSeed.Length]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.CreateAccountWithSeed, data,0); - Array.Copy(baseAccount.KeyBytes, 0, data, 4, 32); - Array.Copy(encodedSeed, 0, data, 36, encodedSeed.Length); - Utils.Int64ToByteArrayLe(lamports, data, 36 + encodedSeed.Length); - Utils.Int64ToByteArrayLe(space, data, 44 + encodedSeed.Length); - Array.Copy(owner.KeyBytes, 0, data, 52 + encodedSeed.Length, 32); + + data.WriteU32((uint) SystemProgramInstructions.CreateAccountWithSeed, 0); + data.WritePubKey(baseAccount, 4); + data.WriteSpan(encodedSeed, 36); + data.WriteU64(lamports, 36 + encodedSeed.Length); + data.WriteU64(space, 44 + encodedSeed.Length); + data.WritePubKey(owner, 52 + encodedSeed.Length); + return data; } @@ -84,7 +91,9 @@ internal static byte[] EncodeCreateAccountWithSeedData( internal static byte[] EncodeAdvanceNonceAccountData() { byte[] data = new byte[4]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.AdvanceNonceAccount, data,0); + + data.WriteU32((uint) SystemProgramInstructions.AdvanceNonceAccount, 0); + return data; } @@ -96,8 +105,10 @@ internal static byte[] EncodeAdvanceNonceAccountData() internal static byte[] EncodeWithdrawNonceAccountData(ulong lamports) { byte[] data = new byte[12]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.WithdrawNonceAccount, data, 0); - Utils.Int64ToByteArrayLe(lamports, data, 4); + + data.WriteU32((uint) SystemProgramInstructions.WithdrawNonceAccount, 0); + data.WriteU64(lamports, 4); + return data; } @@ -109,8 +120,10 @@ internal static byte[] EncodeWithdrawNonceAccountData(ulong lamports) internal static byte[] EncodeInitializeNonceAccountData(PublicKey authorized) { byte[] data = new byte[36]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.InitializeNonceAccount, data, 0); - Array.Copy(authorized.KeyBytes, 0, data, 4, 32); + + data.WriteU32((uint) SystemProgramInstructions.InitializeNonceAccount, 0); + data.WritePubKey(authorized, 4); + return data; } @@ -122,8 +135,10 @@ internal static byte[] EncodeInitializeNonceAccountData(PublicKey authorized) internal static byte[] EncodeAuthorizeNonceAccountData(PublicKey authorized) { byte[] data = new byte[36]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.AuthorizeNonceAccount, data, 0); - Array.Copy(authorized.KeyBytes, 0, data, 4, 32); + + data.WriteU32((uint) SystemProgramInstructions.AuthorizeNonceAccount, 0); + data.WritePubKey(authorized, 4); + return data; } @@ -135,8 +150,10 @@ internal static byte[] EncodeAuthorizeNonceAccountData(PublicKey authorized) internal static byte[] EncodeAllocateData(ulong space) { byte[] data = new byte[12]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.Allocate, data, 0); - Utils.Int64ToByteArrayLe(space, data, 4); + + data.WriteU32((uint) SystemProgramInstructions.Allocate, 0); + data.WriteU64(space, 4); + return data; } @@ -153,11 +170,13 @@ internal static byte[] EncodeAllocateWithSeedData( { byte[] encodedSeed = EncodeRustString(seed); byte[] data = new byte[76 + encodedSeed.Length]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.AllocateWithSeed, data,0); - Array.Copy(baseAccount.KeyBytes, 0, data, 4, 32); - Array.Copy(encodedSeed, 0, data, 36, encodedSeed.Length); - Utils.Int64ToByteArrayLe(space, data, 36 + encodedSeed.Length); - Array.Copy(owner.KeyBytes, 0, data, 44 + encodedSeed.Length, 32); + + data.WriteU32((uint) SystemProgramInstructions.AllocateWithSeed, 0); + data.WritePubKey(baseAccount, 4); + data.WriteSpan(encodedSeed, 36); + data.WriteU64(space, 36 + encodedSeed.Length); + data.WritePubKey(owner, 44 + encodedSeed.Length); + return data; } @@ -173,10 +192,12 @@ internal static byte[] EncodeAssignWithSeedData( { byte[] encodedSeed = EncodeRustString(seed); byte[] data = new byte[68 + encodedSeed.Length]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.AssignWithSeed, data, 0); - Array.Copy(baseAccount.KeyBytes, 0, data, 4, 32); - Array.Copy(encodedSeed, 0, data, 36, encodedSeed.Length); - Array.Copy(owner.KeyBytes, 0, data, 36 + encodedSeed.Length, 32); + + data.WriteU32((uint) SystemProgramInstructions.AssignWithSeed, 0); + data.WritePubKey(baseAccount, 4); + data.WriteSpan(encodedSeed, 36); + data.WritePubKey(owner, 36 + encodedSeed.Length); + return data; } @@ -191,10 +212,12 @@ internal static byte[] EncodeTransferWithSeedData(PublicKey owner, string seed, { byte[] encodedSeed = EncodeRustString(seed); byte[] data = new byte[44 + encodedSeed.Length]; - Utils.Uint32ToByteArrayLe((ulong) SystemProgramInstructions.TransferWithSeed, data, 0); - Utils.Int64ToByteArrayLe(lamports, data, 4); - Array.Copy(encodedSeed, 0, data, 8, encodedSeed.Length); - Array.Copy(owner.KeyBytes, 0, data, 8 + encodedSeed.Length, 32); + + data.WriteU32((uint) SystemProgramInstructions.TransferWithSeed, 0); + data.WriteU64(lamports, 4); + data.WriteSpan(encodedSeed, 12); + data.WritePubKey(owner, 12 + encodedSeed.Length); + return data; } @@ -210,8 +233,9 @@ private static byte[] EncodeRustString(string data) { byte[] stringBytes = Encoding.ASCII.GetBytes(data); byte[] encoded = new byte[stringBytes.Length + 4]; - Utils.Uint32ToByteArrayLe((ulong) stringBytes.Length, encoded, 0); - Array.Copy(stringBytes, 0, encoded, 4, stringBytes.Length); + + encoded.WriteU32((uint) stringBytes.Length, 0); + encoded.WriteSpan(stringBytes, 4); return encoded; } } diff --git a/src/Solnet.Programs/TokenProgram.cs b/src/Solnet.Programs/TokenProgram.cs index 3ce779c6..3a3550e9 100644 --- a/src/Solnet.Programs/TokenProgram.cs +++ b/src/Solnet.Programs/TokenProgram.cs @@ -161,8 +161,8 @@ public static TransactionInstruction InitializeMint(PublicKey mint, int decimals ProgramId = ProgramIdKey.KeyBytes, Keys = keys, Data = TokenProgramData.EncodeInitializeMintData( - mintAuthority.KeyBytes, - freezeAuthority != null ? freezeAuthority.KeyBytes : new Account().PublicKey.KeyBytes, + mintAuthority, + freezeAuthority ?? new Account().PublicKey, decimals, freezeAuthorityOpt) }; @@ -252,7 +252,7 @@ public static TransactionInstruction SetAuthority( { ProgramId = ProgramIdKey.KeyBytes, Keys = keys, - Data = TokenProgramData.EncodeSetAuthorityData(authority, newAuthorityOpt, newAuthority.KeyBytes) + Data = TokenProgramData.EncodeSetAuthorityData(authority, newAuthorityOpt, newAuthority) }; } diff --git a/src/Solnet.Programs/TokenProgramData.cs b/src/Solnet.Programs/TokenProgramData.cs index 2390dcac..5154162a 100644 --- a/src/Solnet.Programs/TokenProgramData.cs +++ b/src/Solnet.Programs/TokenProgramData.cs @@ -1,5 +1,7 @@ // unset +using Solnet.Programs.Utilities; +using Solnet.Wallet; using System; namespace Solnet.Programs @@ -39,15 +41,15 @@ internal static byte[] EncodeApproveData(ulong amount) /// The freezeAuthorityOption parameter is related to the existence or not of a freeze authority. /// The byte array with the encoded data. internal static byte[] EncodeInitializeMintData( - byte[] mintAuthority, byte[] freezeAuthority, int decimals, int freezeAuthorityOption) + PublicKey mintAuthority, PublicKey freezeAuthority, int decimals, int freezeAuthorityOption) { byte[] methodBuffer = new byte[67]; - methodBuffer[0] = (byte)TokenProgramInstructions.InitializeMint; - methodBuffer[1] = (byte)decimals; - Array.Copy(mintAuthority, 0, methodBuffer, 2, 32); - methodBuffer[34] = (byte)freezeAuthorityOption; - Array.Copy(freezeAuthority, 0, methodBuffer, 35, 32); + methodBuffer.WriteU8((byte)TokenProgramInstructions.InitializeMint, 0); + methodBuffer.WriteU8((byte)decimals, 1); + methodBuffer.WritePubKey(mintAuthority, 2); + methodBuffer.WriteU8((byte)freezeAuthorityOption, 34); + methodBuffer.WritePubKey(freezeAuthority, 35); return methodBuffer; } @@ -86,8 +88,8 @@ internal static byte[] EncodeInitializeMultiSignatureData(int m) { byte[] methodBuffer = new byte[2]; - methodBuffer[0] = (byte)TokenProgramInstructions.InitializeMultiSignature; - methodBuffer[1] = (byte)m; + methodBuffer.WriteU8((byte)TokenProgramInstructions.InitializeMultiSignature, 0); + methodBuffer.WriteU8((byte)m, 1); return methodBuffer; } @@ -96,14 +98,14 @@ internal static byte[] EncodeInitializeMultiSignatureData(int m) /// Encode the transaction instruction data for the method. /// /// The byte array with the encoded data. - internal static byte[] EncodeSetAuthorityData(AuthorityType authorityType, int newAuthorityOption, byte[] newAuthority) + internal static byte[] EncodeSetAuthorityData(AuthorityType authorityType, int newAuthorityOption, PublicKey newAuthority) { byte[] methodBuffer = new byte[35]; - methodBuffer[0] = (byte)TokenProgramInstructions.SetAuthority; - methodBuffer[1] = (byte)authorityType; - methodBuffer[2] = (byte)newAuthorityOption; - Array.Copy(newAuthority, 0, methodBuffer, 3, 32); + methodBuffer.WriteU8((byte)TokenProgramInstructions.SetAuthority, 0); + methodBuffer.WriteU8((byte)authorityType, 1); + methodBuffer.WriteU8((byte)newAuthorityOption, 2); + methodBuffer.WritePubKey(newAuthority, 3); return methodBuffer; } @@ -170,8 +172,10 @@ internal static byte[] EncodeBurnCheckedData(ulong amount, int decimals) private static byte[] EncodeAmountLayout(byte method, ulong amount) { byte[] methodBuffer = new byte[9]; - methodBuffer[0] = method; - Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); + + methodBuffer.WriteU8(method, 0); + methodBuffer.WriteU64(amount, 1); + return methodBuffer; } @@ -185,9 +189,11 @@ private static byte[] EncodeAmountLayout(byte method, ulong amount) private static byte[] EncodeAmountCheckedLayout(byte method, ulong amount, byte decimals) { byte[] methodBuffer = new byte[10]; - methodBuffer[0] = method; - Utils.Int64ToByteArrayLe(amount, methodBuffer, 1); - methodBuffer[9] = decimals; + + methodBuffer.WriteU8(method, 0); + methodBuffer.WriteU64(amount, 1); + methodBuffer.WriteU8(decimals, 9); + return methodBuffer; } } diff --git a/src/Solnet.Programs/Utilities/Deserialization.cs b/src/Solnet.Programs/Utilities/Deserialization.cs new file mode 100644 index 00000000..897dc3c4 --- /dev/null +++ b/src/Solnet.Programs/Utilities/Deserialization.cs @@ -0,0 +1,176 @@ +using Solnet.Wallet; +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Numerics; + +namespace Solnet.Programs.Utilities +{ + /// + /// Extension methods for deserialization of program data using . + /// + public static class Deserialization + { + /// + /// Get a 8-bit unsigned integer from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 8-bit unsigned integer begins. + /// The 8-bit unsigned integer. + /// Thrown when the offset is too big for the span. + public static byte GetU8(this ReadOnlySpan data, int offset) + { + if (offset > data.Length - 1) + throw new ArgumentOutOfRangeException(nameof(offset)); + return data[offset]; + } + + /// + /// Get a 16-bit unsigned integer from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 16-bit unsigned integer begins. + /// The 16-bit unsigned integer. + /// Thrown when the offset is too big for the span. + public static ushort GetU16(this ReadOnlySpan data, int offset) + { + if (offset + 2 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + return BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(offset, 2)); + } + + /// + /// Get a 32-bit unsigned integer from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 32-bit unsigned integer begins. + /// The 32-bit unsigned integer. + /// Thrown when the offset is too big for the span. + public static uint GetU32(this ReadOnlySpan data, int offset) + { + if (offset + 4 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + return BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(offset, 4)); + } + + /// + /// Get a 64-bit unsigned integer from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 64-bit unsigned integer begins. + /// The 64-bit unsigned integer. + /// Thrown when the offset is too big for the span. + public static ulong GetU64(this ReadOnlySpan data, int offset) + { + if (offset + 8 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + return BinaryPrimitives.ReadUInt64LittleEndian(data.Slice(offset, 8)); + } + + /// + /// Get a 8-bit signed integer from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 8-bit signed integer begins. + /// The 8-bit signed integer. + /// Thrown when the offset is too big for the span. + public static sbyte GetS8(this ReadOnlySpan data, int offset) + { + if (offset > data.Length - 1) + throw new ArgumentOutOfRangeException(nameof(offset)); + return (sbyte)data[offset]; + } + + /// + /// Get a 16-bit signed integer from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 16-bit signed integer begins. + /// The 16-bit signed integer. + /// Thrown when the offset is too big for the span. + public static short GetS16(this ReadOnlySpan data, int offset) + { + if (offset + 2 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + return BinaryPrimitives.ReadInt16LittleEndian(data.Slice(offset, 2)); + } + + /// + /// Get a 32-bit signed integer from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 32-bit signed integer begins. + /// The 32-bit signed integer. + /// Thrown when the offset is too big for the span. + public static int GetS32(this ReadOnlySpan data, int offset) + { + if (offset + 4 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + return BinaryPrimitives.ReadInt32LittleEndian(data.Slice(offset, 4)); + } + + /// + /// Get a 64-bit signed integer from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 64-bit signed integer begins. + /// The 64-bit signed integer. + /// Thrown when the offset is too big for the span. + public static long GetS64(this ReadOnlySpan data, int offset) + { + if (offset + 8 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + return BinaryPrimitives.ReadInt64LittleEndian(data.Slice(offset, 8)); + } + + /// + /// Get a span from the read-only span at the given offset with the given length. + /// + /// The span to get data from. + /// The offset at which the desired span begins. + /// The desired length for the new span. + /// A of bytes. + /// Thrown when the offset is too big for the span. + public static Span GetSpan(this ReadOnlySpan data, int offset, int length) + { + if (offset + length > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + byte[] buffer = new byte[length]; + data.Slice(offset, length).CopyTo(buffer); + return buffer; + } + + /// + /// Get a encoded as a 32 byte array from the span at the given offset. + /// + /// The span to get data from. + /// The offset at which the 32 byte array begins. + /// The instance. + /// Thrown when the offset is too big for the span. + public static PublicKey GetPubKey(this ReadOnlySpan data, int offset) + { + if (offset + 32 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + return new PublicKey(data.Slice(offset, 32).ToArray()); + } + + /// + /// Get an arbitrarily long number from the span at the given offset, specifying it's length in bytes. + /// Optionally specify if it's signed and the endianness. + /// + /// The span to get data from. + /// The offset at which the arbitrarily long number begins. + /// The byte-length of the arbitrarily long number. + /// Whether the value uses signed encoding. + /// Whether the value is in big-endian byte order. + /// The instance that represents the value. + /// Thrown when the offset is too big for the span. + public static BigInteger GetBigInt(this ReadOnlySpan data, int offset, int length, + bool isSigned = false, bool isBigEndian = false) + { + if (offset + length > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + return new BigInteger(data.Slice(offset, length), isSigned, isBigEndian); + } + } +} \ No newline at end of file diff --git a/src/Solnet.Programs/Utilities/Serialization.cs b/src/Solnet.Programs/Utilities/Serialization.cs new file mode 100644 index 00000000..4bad79b1 --- /dev/null +++ b/src/Solnet.Programs/Utilities/Serialization.cs @@ -0,0 +1,190 @@ +using Solnet.Wallet; +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Numerics; + +namespace Solnet.Programs.Utilities +{ + /// + /// Extension methods for serialization of program data using . + /// + public static class Serialization + { + /// + /// Write a 8-bit unsigned integer to the byte array at the given offset. + /// + /// The span to get data from. + /// The 8-bit unsigned integer value to write. + /// The offset at which to write the 8-bit unsigned integer. + /// The 8-bit unsigned integer. + /// Thrown when the offset is too big for the data array. + public static void WriteU8(this byte[] data, byte value, int offset) + { + if (offset > data.Length - 1) + throw new ArgumentOutOfRangeException(nameof(offset)); + data[offset] = value; + } + + /// + /// Write a 16-bit unsigned integer to the byte array at the given offset. + /// + /// The span to get data from. + /// The 16-bit unsigned integer value to write. + /// The offset at which to write the 16-bit unsigned integer. + /// The 16-bit unsigned integer. + /// Thrown when the offset is too big for the data array. + public static void WriteU16(this byte[] data, ushort value, int offset) + { + if (offset + 2 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + BinaryPrimitives.WriteUInt16LittleEndian(data.AsSpan(offset, 2), value); + } + + /// + /// Write a 32-bit unsigned integer to the byte array at the given offset. + /// + /// The span to get data from. + /// The 32-bit unsigned integer value to write. + /// The offset at which to write the 32-bit unsigned integer. + /// The 32-bit unsigned integer. + /// Thrown when the offset is too big for the data array. + public static void WriteU32(this byte[] data, uint value, int offset) + { + if (offset + 4 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(offset, 4), value); + } + + /// + /// Write a 64-bit unsigned integer to the byte array at the given offset. + /// + /// The span to get data from. + /// The 64-bit unsigned integer value to write. + /// The offset at which to write the 64-bit unsigned integer. + /// The 64-bit unsigned integer. + /// Thrown when the offset is too big for the data array. + public static void WriteU64(this byte[] data, ulong value, int offset) + { + if (offset + 8 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + BinaryPrimitives.WriteUInt64LittleEndian(data.AsSpan(offset, 8), value); + } + + /// + /// Write a 8-bit signed integer to the byte array at the given offset. + /// + /// The span to get data from. + /// The 8-bit signed integer value to write. + /// The offset at which to write the 8-bit signed integer. + /// The 8-bit signed integer. + /// Thrown when the offset is too big for the data array. + public static void WriteS8(this byte[] data, sbyte value, int offset) + { + if (offset > data.Length - 1) + throw new ArgumentOutOfRangeException(nameof(offset)); + data[offset] = (byte) value; + } + + /// + /// Write a 16-bit signed integer to the byte array at the given offset. + /// + /// The span to get data from. + /// The 16-bit signed integer value to write. + /// The offset at which to write the 16-bit signed integer. + /// The 16-bit signed integer. + /// Thrown when the offset is too big for the data array. + public static void WriteS16(this byte[] data, short value, int offset) + { + if (offset + 2 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + BinaryPrimitives.WriteInt16LittleEndian(data.AsSpan(offset, 2), value); + } + + /// + /// Write a 32-bit signed integer to the byte array at the given offset. + /// + /// The span to get data from. + /// The 32-bit signed integer value to write. + /// The offset at which to write the 32-bit signed integer. + /// The 32-bit signed integer. + /// Thrown when the offset is too big for the data array. + public static void WriteS32(this byte[] data, int value, int offset) + { + if (offset + 4 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan(offset, 4), value); + } + + /// + /// Write a 64-bit signed integer to the byte array at the given offset. + /// + /// The span to get data from. + /// The 64-bit signed integer value to write. + /// The offset at which to write the 64-bit signed integer. + /// The 64-bit signed integer. + /// Thrown when the offset is too big for the data array. + public static void WriteS64(this byte[] data, long value, int offset) + { + if (offset + 8 > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + BinaryPrimitives.WriteInt64LittleEndian(data.AsSpan(offset, 8), value); + } + + /// + /// Write a span of bytes to the byte array at the given offset. + /// + /// The span to get data from. + /// The to write. + /// The offset at which to write the . + /// The instance. + /// Thrown when the offset is too big for the data array. + public static void WriteSpan(this byte[] data, ReadOnlySpan span, int offset) + { + if (offset + span.Length > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + span.CopyTo(data.AsSpan(offset, span.Length)); + } + + /// + /// Write a encoded as a 32 byte array to the byte array at the given offset. + /// + /// The span to get data from. + /// The to write. + /// The offset at which to write the . + /// The instance. + /// Thrown when the offset is too big for the data array. + public static void WritePubKey(this byte[] data, PublicKey publicKey, int offset) + { + if (offset + publicKey.KeyBytes.Length > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + publicKey.KeyBytes.AsSpan().CopyTo(data.AsSpan(offset, publicKey.KeyBytes.Length)); + } + + /// + /// Write an arbitrarily long number to the byte array at the given offset, specifying it's length in bytes. + /// Optionally specify if it's signed and the endianness. + /// + /// The byte array to get data from. + /// The to write. + /// The offset at which to write the . + /// Whether the value uses signed encoding. + /// Whether the value is in big-endian byte order. + /// The instance that represents the value. + /// Thrown when the offset is too big for the data array. + public static int WriteBigInt(this byte[] data, BigInteger bigInteger, int offset, + bool isSigned = false, bool isBigEndian = false) + { + int byteCount = bigInteger.GetByteCount(isSigned); + if (offset + byteCount > data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + + bigInteger.TryWriteBytes( + data.AsSpan(offset, byteCount), + out int written, + isSigned, + isBigEndian); + return written; + } + } +} \ No newline at end of file diff --git a/src/Solnet.Programs/Utils.cs b/src/Solnet.Programs/Utils.cs deleted file mode 100644 index 6a5a2a96..00000000 --- a/src/Solnet.Programs/Utils.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Solnet.Programs -{ - /// - /// Utilities class for programs message encoding. - /// - public static class Utils - { - /// - /// Write 4 bytes to the byte array (starting at the offset) as unsigned 32-bit integer in little endian format. - /// - /// The value to write. - /// The array to write in. - /// The offset at which to start writing. - public static void Uint32ToByteArrayLe(ulong val, byte[] array, int offset) - { - array[offset] = (byte)(0xFF & val); - array[offset + 1] = (byte)(0xFF & (val >> 8)); - array[offset + 2] = (byte)(0xFF & (val >> 16)); - array[offset + 3] = (byte)(0xFF & (val >> 24)); - } - - /// - /// Write 8 bytes to the byte array (starting at the offset) as signed 64-bit integer in little endian format. - /// - /// The value to write. - /// The array to write in. - /// The offset at which to start writing. - public static void Int64ToByteArrayLe(ulong val, byte[] array, int offset) - { - array[offset] = (byte)(0xFF & val); - array[offset + 1] = (byte)(0xFF & (val >> 8)); - array[offset + 2] = (byte)(0xFF & (val >> 16)); - array[offset + 3] = (byte)(0xFF & (val >> 24)); - array[offset + 4] = (byte)(0xFF & (val >> 32)); - array[offset + 5] = (byte)(0xFF & (val >> 40)); - array[offset + 6] = (byte)(0xFF & (val >> 48)); - array[offset + 7] = (byte)(0xFF & (val >> 56)); - } - } -} \ No newline at end of file diff --git a/test/Solnet.Programs.Test/Abstract/ByteFlagTest.cs b/test/Solnet.Programs.Test/Abstract/ByteFlagTest.cs new file mode 100644 index 00000000..4e4a6034 --- /dev/null +++ b/test/Solnet.Programs.Test/Abstract/ByteFlagTest.cs @@ -0,0 +1,129 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Solnet.Programs.Abstract; + +namespace Solnet.Programs.Test.Abstract +{ + [TestClass] + public class ByteFlagTest + { + [TestMethod] + public void TestBit0() + { + ByteFlag sut = new (1); + Assert.AreEqual(1, sut.Value); + Assert.IsTrue(sut.Bit0); + Assert.IsFalse(sut.Bit1); + Assert.IsFalse(sut.Bit2); + Assert.IsFalse(sut.Bit3); + Assert.IsFalse(sut.Bit4); + Assert.IsFalse(sut.Bit5); + Assert.IsFalse(sut.Bit6); + Assert.IsFalse(sut.Bit7); + } + + [TestMethod] + public void TestBit1() + { + ByteFlag sut = new (2); + Assert.AreEqual(2, sut.Value); + Assert.IsFalse(sut.Bit0); + Assert.IsTrue(sut.Bit1); + Assert.IsFalse(sut.Bit2); + Assert.IsFalse(sut.Bit3); + Assert.IsFalse(sut.Bit4); + Assert.IsFalse(sut.Bit5); + Assert.IsFalse(sut.Bit6); + Assert.IsFalse(sut.Bit7); + } + + [TestMethod] + public void TestBit2() + { + ByteFlag sut = new (4); + Assert.AreEqual(4, sut.Value); + Assert.IsFalse(sut.Bit0); + Assert.IsFalse(sut.Bit1); + Assert.IsTrue(sut.Bit2); + Assert.IsFalse(sut.Bit3); + Assert.IsFalse(sut.Bit4); + Assert.IsFalse(sut.Bit5); + Assert.IsFalse(sut.Bit6); + Assert.IsFalse(sut.Bit7); + } + + [TestMethod] + public void TestBit3() + { + ByteFlag sut = new (8); + Assert.AreEqual(8, sut.Value); + Assert.IsFalse(sut.Bit0); + Assert.IsFalse(sut.Bit1); + Assert.IsFalse(sut.Bit2); + Assert.IsTrue(sut.Bit3); + Assert.IsFalse(sut.Bit4); + Assert.IsFalse(sut.Bit5); + Assert.IsFalse(sut.Bit6); + Assert.IsFalse(sut.Bit7); + } + + [TestMethod] + public void TestBit4() + { + ByteFlag sut = new (16); + Assert.AreEqual(16, sut.Value); + Assert.IsFalse(sut.Bit0); + Assert.IsFalse(sut.Bit1); + Assert.IsFalse(sut.Bit2); + Assert.IsFalse(sut.Bit3); + Assert.IsTrue(sut.Bit4); + Assert.IsFalse(sut.Bit5); + Assert.IsFalse(sut.Bit6); + Assert.IsFalse(sut.Bit7); + } + + [TestMethod] + public void TestBit5() + { + ByteFlag sut = new (32); + Assert.AreEqual(32, sut.Value); + Assert.IsFalse(sut.Bit0); + Assert.IsFalse(sut.Bit1); + Assert.IsFalse(sut.Bit2); + Assert.IsFalse(sut.Bit3); + Assert.IsFalse(sut.Bit4); + Assert.IsTrue(sut.Bit5); + Assert.IsFalse(sut.Bit6); + Assert.IsFalse(sut.Bit7); + } + + [TestMethod] + public void TestBit6() + { + ByteFlag sut = new (64); + Assert.AreEqual(64, sut.Value); + Assert.IsFalse(sut.Bit0); + Assert.IsFalse(sut.Bit1); + Assert.IsFalse(sut.Bit2); + Assert.IsFalse(sut.Bit3); + Assert.IsFalse(sut.Bit4); + Assert.IsFalse(sut.Bit5); + Assert.IsTrue(sut.Bit6); + Assert.IsFalse(sut.Bit7); + } + + [TestMethod] + public void TestBit7() + { + ByteFlag sut = new (128); + Assert.AreEqual(128, sut.Value); + Assert.IsFalse(sut.Bit0); + Assert.IsFalse(sut.Bit1); + Assert.IsFalse(sut.Bit2); + Assert.IsFalse(sut.Bit3); + Assert.IsFalse(sut.Bit4); + Assert.IsFalse(sut.Bit5); + Assert.IsFalse(sut.Bit6); + Assert.IsTrue(sut.Bit7); + } + } +} \ No newline at end of file diff --git a/test/Solnet.Programs.Test/SystemProgramTest.cs b/test/Solnet.Programs.Test/SystemProgramTest.cs index a94a387c..552a36ba 100644 --- a/test/Solnet.Programs.Test/SystemProgramTest.cs +++ b/test/Solnet.Programs.Test/SystemProgramTest.cs @@ -100,10 +100,10 @@ public class SystemProgramTest private static readonly byte[] TransferWithSeedInstructionBytes = { - 11, 0, 0, 0, 64, 66, 15, 0, 8, 0, 0, 0, 116, 101, 115, 116, + 11, 0, 0, 0, 64, 66, 15, 0, 0, 0, 0, 0, 8, 0, 0, 0, 116, 101, 115, 116, 83, 101, 101, 100, 4, 23, 154, 206, 58, 166, 9, 125, 107, 80, 224, 57, 235, 71, 51, 46, 27, 153, 48, 39, 162, 54, 144, 176, - 6, 128, 214, 189, 53, 152, 48, 38, 0, 0, 0, 0 + 6, 128, 214, 189, 53, 152, 48, 38, }; private const long BalanceForRentExemption = 2039280L; diff --git a/test/Solnet.Programs.Test/Utilities/DeserializationUtilitiesTest.cs b/test/Solnet.Programs.Test/Utilities/DeserializationUtilitiesTest.cs new file mode 100644 index 00000000..c0026307 --- /dev/null +++ b/test/Solnet.Programs.Test/Utilities/DeserializationUtilitiesTest.cs @@ -0,0 +1,212 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Solnet.Programs.Utilities; +using Solnet.Wallet; +using System; +using System.Numerics; + +namespace Solnet.Programs.Test.Utilities +{ + [TestClass] + public class DeserializationUtilitiesTest + { + private static readonly byte[] PublicKeyBytes = + { + 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, + 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, + 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 + }; + + private static readonly byte[] BigIntBytes = + { + 153, 153, 153, 153, 153, 153, 153, 153, + 153, 153, 153, 153, 153, 153, 153, 25, + }; + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadU8Exception() + { + ReadOnlySpan readSpan = new byte[] {1}.AsSpan(); + byte value = readSpan.GetU8(1); + } + + [TestMethod] + public void TestReadU8() + { + ReadOnlySpan readSpan = new byte[] {1}.AsSpan(); + byte value = readSpan.GetU8(0); + + Assert.AreEqual(1, value); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadU16Exception() + { + ReadOnlySpan readSpan = new byte[] {1, 0}.AsSpan(); + uint value = readSpan.GetU16(1); + } + + [TestMethod] + public void TestReadU16() + { + ReadOnlySpan readSpan = new byte[] {1, 0}.AsSpan(); + uint value = readSpan.GetU16(0); + + Assert.AreEqual(1U, value); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadU32Exception() + { + ReadOnlySpan readSpan = new byte[] {1, 0, 0, 0}.AsSpan(); + uint value = readSpan.GetU32(1); + } + + [TestMethod] + public void TestReadU32() + { + ReadOnlySpan readSpan = new byte[] {1, 0, 0, 0}.AsSpan(); + uint value = readSpan.GetU32(0); + + Assert.AreEqual(1U, value); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadU64Exception() + { + ReadOnlySpan readSpan = new byte[] {1, 0, 0, 0, 0, 0, 0, 0}.AsSpan(); + ulong value = readSpan.GetU64(1); + } + + [TestMethod] + public void TestReadU64() + { + ReadOnlySpan readSpan = new byte[] {1, 0, 0, 0, 0, 0, 0, 0}.AsSpan(); + ulong value = readSpan.GetU64(0); + + Assert.AreEqual(1UL, value); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadS8Exception() + { + ReadOnlySpan readSpan = new byte[] {1}.AsSpan(); + sbyte value = readSpan.GetS8(1); + } + + [TestMethod] + public void TestReadS8() + { + ReadOnlySpan readSpan = new byte[] {1}.AsSpan(); + sbyte value = readSpan.GetS8(0); + + Assert.AreEqual(1, value); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadS16Exception() + { + ReadOnlySpan readSpan = new byte[] {1, 0}.AsSpan(); + int value = readSpan.GetS16(1); + } + + [TestMethod] + public void TestReadS16() + { + ReadOnlySpan readSpan = new byte[] {1, 0}.AsSpan(); + int value = readSpan.GetS16(0); + + Assert.AreEqual(1, value); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadS32Exception() + { + ReadOnlySpan readSpan = new byte[] {1, 0, 0, 0}.AsSpan(); + int value = readSpan.GetS32(1); + } + + [TestMethod] + public void TestReadS32() + { + ReadOnlySpan readSpan = new byte[] {1, 0, 0, 0}.AsSpan(); + int value = readSpan.GetS32(0); + + Assert.AreEqual(1, value); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadS64Exception() + { + ReadOnlySpan readSpan = new byte[] {1, 0, 0, 0, 0, 0, 0, 0}.AsSpan(); + long value = readSpan.GetS64(1); + } + + [TestMethod] + public void TestReadS64() + { + ReadOnlySpan readSpan = new byte[] {1, 0, 0, 0, 0, 0, 0, 0}.AsSpan(); + long value = readSpan.GetS64(0); + + Assert.AreEqual(1L, value); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadSpanException() + { + ReadOnlySpan readSpan = PublicKeyBytes.AsSpan(); + Span span = readSpan.GetSpan(1, 32); + } + + [TestMethod] + public void TestReadSpan() + { + ReadOnlySpan readSpan = PublicKeyBytes.AsSpan(); + Span span = readSpan.GetSpan(0, 32); + + CollectionAssert.AreEqual(PublicKeyBytes, span.ToArray()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadPublicKeyException() + { + ReadOnlySpan span = PublicKeyBytes.AsSpan(); + PublicKey pk = span.GetPubKey(1); + } + + [TestMethod] + public void TestReadPublicKey() + { + ReadOnlySpan span = PublicKeyBytes.AsSpan(); + PublicKey pk = span.GetPubKey(0); + + Assert.AreEqual("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", pk.Key); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestReadBigIntegerException() + { + ReadOnlySpan span = BigIntBytes.AsSpan(); + BigInteger bi = span.GetBigInt(1, 16); + } + + [TestMethod] + public void TestReadBigInteger() + { + BigInteger actual = new(BigIntBytes); + ReadOnlySpan span = BigIntBytes.AsSpan(); + BigInteger bi = span.GetBigInt(0, 16); + Assert.AreEqual(actual, bi); + } + } +} \ No newline at end of file diff --git a/test/Solnet.Programs.Test/Utilities/SerializationUtilitiesTest.cs b/test/Solnet.Programs.Test/Utilities/SerializationUtilitiesTest.cs new file mode 100644 index 00000000..f5484da7 --- /dev/null +++ b/test/Solnet.Programs.Test/Utilities/SerializationUtilitiesTest.cs @@ -0,0 +1,202 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Solnet.Programs.Utilities; +using Solnet.Wallet; +using System; +using System.Numerics; + +namespace Solnet.Programs.Test.Utilities +{ + [TestClass] + public class SerializationUtilitiesTest + { + private static readonly byte[] PublicKeyBytes = + { + 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, + 225, 70, 206, 235, 121, 172, 28, 180, 133, 237, + 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169 + }; + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteU8Exception() + { + byte[] sut = new byte[1]; + sut.WriteU8(1, 2); + } + + [TestMethod] + public void TestWriteU8() + { + byte[] sut = new byte[1]; + sut.WriteU8(1, 0); + CollectionAssert.AreEqual(new byte[]{ 1 }, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteU16Exception() + { + byte[] sut = new byte[2]; + sut.WriteU16(1, 2); + } + + [TestMethod] + public void TestWriteU16() + { + byte[] sut = new byte[2]; + sut.WriteU16(1, 0); + CollectionAssert.AreEqual(new byte[]{ 1, 0 }, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteU32Exception() + { + byte[] sut = new byte[4]; + sut.WriteU32(1, 4); + } + + [TestMethod] + public void TestWriteU32() + { + byte[] sut = new byte[4]; + sut.WriteU32(1, 0); + CollectionAssert.AreEqual(new byte[]{1, 0, 0, 0}, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteU64Exception() + { + byte[] sut = new byte[8]; + sut.WriteU64(1, 8); + } + + [TestMethod] + public void TestWriteU64() + { + byte[] sut = new byte[8]; + sut.WriteU64(1, 0); + CollectionAssert.AreEqual(new byte[]{1, 0, 0, 0, 0, 0, 0, 0}, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteS8Exception() + { + byte[] sut = new byte[1]; + sut.WriteS8(1, 2); + } + + [TestMethod] + public void TestWriteS8() + { + byte[] sut = new byte[1]; + sut.WriteS8(1, 0); + CollectionAssert.AreEqual(new byte[]{1}, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteS16Exception() + { + byte[] sut = new byte[2]; + sut.WriteS16(1, 1); + } + + [TestMethod] + public void TestWriteS16() + { + byte[] sut = new byte[2]; + sut.WriteS16(1, 0); + CollectionAssert.AreEqual(new byte[]{1, 0}, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteS32Exception() + { + byte[] sut = new byte[4]; + sut.WriteS32(1, 1); + } + + [TestMethod] + public void TestWriteS32() + { + byte[] sut = new byte[4]; + sut.WriteS32(1, 0); + CollectionAssert.AreEqual(new byte[]{1, 0, 0, 0}, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteS64Exception() + { + byte[] sut = new byte[8]; + sut.WriteS64(1, 1); + } + + [TestMethod] + public void TestWriteS64() + { + byte[] sut = new byte[8]; + sut.WriteS64(1, 0); + CollectionAssert.AreEqual(new byte[]{1, 0, 0, 0, 0, 0, 0, 0}, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteSpanException() + { + byte[] sut = new byte[32]; + sut.WriteSpan(PublicKeyBytes, 1); + } + + [TestMethod] + public void TestWriteSpan() + { + byte[] sut = new byte[32]; + sut.WriteSpan(PublicKeyBytes, 0); + CollectionAssert.AreEqual(PublicKeyBytes, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWritePublicKeyException() + { + byte[] sut = new byte[32]; + sut.WritePubKey(new PublicKey(PublicKeyBytes), 1); + } + + [TestMethod] + public void TestWritePublicKey() + { + byte[] sut = new byte[32]; + sut.WritePubKey(new PublicKey(PublicKeyBytes), 0); + CollectionAssert.AreEqual(PublicKeyBytes, sut); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void TestWriteBigIntegerException() + { + byte[] sut = new byte[16]; + sut.WriteBigInt(new BigInteger(15000000000000000000000000D), 8); + } + + [TestMethod] + public void TestWriteBigInteger() + { + byte[] sut = new byte[16]; + BigInteger bi = BigInteger.Parse("34028236692093846346337460743176821145"); + + int written = sut.WriteBigInt(bi, 0); + + Assert.AreEqual(bi.GetByteCount(), written); + CollectionAssert.AreEqual(new byte[] + { + 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 25, + }, sut); + } + } +} \ No newline at end of file