Skip to content

Commit

Permalink
Enhancements and utilities (#138)
Browse files Browse the repository at this point in the history
* Added extension methods to facilitate program data encoding and decoding and abstract class for bit masks.
  • Loading branch information
hoakbuilds committed Jul 2, 2021
1 parent d571c80 commit c24a32a
Show file tree
Hide file tree
Showing 16 changed files with 1,160 additions and 183 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
<a href="">
<img src="https://img.shields.io/github/license/bmresearch/solnet?style=flat-square"
alt="Code License"></a>
<br>
<a href="https://twitter.com/intent/follow?screen_name=blockmountainio">
<img src="https://img.shields.io/twitter/follow/blockmountainio?style=flat-square&logo=twitter"
alt="Follow on Twitter"></a>
Expand Down
54 changes: 54 additions & 0 deletions src/Solnet.Programs/Abstract/ByteFlag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace Solnet.Programs.Abstract
{
/// <summary>
/// Represents a flag using a byte for masking.
/// </summary>
public class ByteFlag : Flag<byte>
{
/// <summary>
/// Check if the 1st bit is set.
/// </summary>
public bool Bit0 => (Value & 0x01) != 0;

/// <summary>
/// Check if the 2nd bit is set.
/// </summary>
public bool Bit1 => (Value & 0x02) != 0;

/// <summary>
/// Check if the 3rc bit is set.
/// </summary>
public bool Bit2 => (Value & 0x04) != 0;

/// <summary>
/// Check if the 4th bit is set.
/// </summary>
public bool Bit3 => (Value & 0x08) != 0;

/// <summary>
/// Check if the 5th bit is set.
/// </summary>
public bool Bit4 => (Value & 0x10) != 0;

/// <summary>
/// Check if the 6th bit is set.
/// </summary>
public bool Bit5 => (Value & 0x20) != 0;

/// <summary>
/// Check if the 7th bit is set.
/// </summary>
public bool Bit6 => (Value & 0x40) != 0;

/// <summary>
/// Check if the 8th bit is set.
/// </summary>
public bool Bit7 => (Value & 0x80) != 0;

/// <summary>
/// Initialize the flags with the given byte.
/// </summary>
/// <param name="mask">The byte to use.</param>
public ByteFlag(byte mask) : base(mask) { }
}
}
22 changes: 22 additions & 0 deletions src/Solnet.Programs/Abstract/Flag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Solnet.Programs.Abstract
{
/// <summary>
/// Represents bitmask flags for various types of accounts within Solana Programs.
/// </summary>
public abstract class Flag<T>
{
/// <summary>
/// The mask for the account flags.
/// </summary>
public T Value { get; }

/// <summary>
/// Initialize the flags with the given mask.
/// </summary>
/// <param name="mask">The mask to use.</param>
protected Flag(T mask)
{
Value = mask;
}
}
}
6 changes: 3 additions & 3 deletions src/Solnet.Programs/MemoProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public static class MemoProgram
/// <returns>The <see cref="TransactionInstruction"/> which includes the memo data.</returns>
public static TransactionInstruction NewMemo(Account account, string memo)
{
var keys = new List<AccountMeta>
List<AccountMeta> keys = new ()
{
new (account, false)
new AccountMeta(account, false)
};
var memoBytes = Encoding.UTF8.GetBytes(memo);
byte[] memoBytes = Encoding.UTF8.GetBytes(memo);

return new TransactionInstruction
{
Expand Down
50 changes: 26 additions & 24 deletions src/Solnet.Programs/NameServiceProgram.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Solnet.Programs.Utilities;
using Solnet.Rpc.Models;
using Solnet.Rpc.Utilities;
using Solnet.Wallet;
Expand Down Expand Up @@ -45,13 +46,11 @@ public static class NameServiceProgram
/// <returns>The transaction instruction.</returns>
/// <exception cref="Exception">Thrown when it was not possible to derive a program address for the account.</exception>
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);
Expand All @@ -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);
Expand All @@ -78,7 +76,7 @@ public static byte[] ComputeHashedName(string name)
/// <param name="nameClass">The account of the name class.</param>
/// <param name="parentName">The public key of the parent name.</param>
/// <returns>The program derived address for the name.</returns>
public static PublicKey DeriveNameAccountKey(byte[] hashedName, PublicKey nameClass = null, PublicKey parentName = null)
public static PublicKey DeriveNameAccountKey(ReadOnlySpan<byte> hashedName, PublicKey nameClass = null, PublicKey parentName = null)
{
byte[] nameClassKey = new byte[32];
byte[] parentNameKeyBytes = new byte[32];
Expand All @@ -89,7 +87,7 @@ public static PublicKey DeriveNameAccountKey(byte[] hashedName, PublicKey nameCl
try
{
(byte[] nameAccountPublicKey, _) = AddressExtensions.FindProgramAddress(
new List<byte[]> {hashedName, nameClassKey, parentNameKeyBytes}, ProgramIdKey.KeyBytes);
new List<byte[]> {hashedName.ToArray(), nameClassKey, parentNameKeyBytes}, ProgramIdKey.KeyBytes);
return new PublicKey(nameAccountPublicKey);
}
catch (Exception)
Expand All @@ -113,7 +111,7 @@ public static PublicKey DeriveNameAccountKey(byte[] hashedName, PublicKey nameCl
/// <param name="parentNameOwner">The account of the parent name owner.</param>
/// <returns>The transaction instruction.</returns>
private static TransactionInstruction CreateNameRegistryInstruction(
PublicKey nameKey, PublicKey nameOwner, Account payer, byte[] hashedName, ulong lamports, int space,
PublicKey nameKey, PublicKey nameOwner, Account payer, ReadOnlySpan<byte> hashedName, ulong lamports, uint space,
Account nameClass = null, Account parentNameOwner = null, PublicKey parentName = null)
{
List<AccountMeta> keys = new()
Expand Down Expand Up @@ -151,7 +149,7 @@ private static TransactionInstruction CreateNameRegistryInstruction(
/// <param name="nameClass">The account of the name class.</param>
/// <returns>The transaction instruction.</returns>
public static TransactionInstruction UpdateNameRegistry(
PublicKey nameKey, int offset, byte[] data, Account nameOwner = null, Account nameClass = null)
PublicKey nameKey, uint offset, ReadOnlySpan<byte> data, Account nameOwner = null, Account nameClass = null)
{
List<AccountMeta> keys = new() {new AccountMeta(nameKey, true)};

Expand Down Expand Up @@ -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)
};
}

Expand Down Expand Up @@ -228,15 +226,15 @@ public static TransactionInstruction DeleteNameRegistry(
/// <param name="lamports">The number of lamports for rent exemption.</param>
/// <param name="space">The space for the account.</param>
/// <returns>The transaction instruction data.</returns>
private static byte[] EncodeCreateNameRegistryData(byte[] hashedName, ulong lamports, int space)
private static byte[] EncodeCreateNameRegistryData(ReadOnlySpan<byte> 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;
}
Expand All @@ -247,27 +245,29 @@ private static byte[] EncodeCreateNameRegistryData(byte[] hashedName, ulong lamp
/// <param name="offset">The offset at which to update the data.</param>
/// <param name="data">The data to insert.</param>
/// <returns>The transaction instruction data.</returns>
private static byte[] EncodeUpdateNameRegistryData(int offset, byte[] data)
private static byte[] EncodeUpdateNameRegistryData(uint offset, ReadOnlySpan<byte> 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;
}

/// <summary>
/// Encode the instruction data to be used with the <see cref="NameServiceInstructions.Transfer"/> instruction.
/// </summary>
/// <param name="publicKey">The public key of the account to transfer ownership to.</param>
/// <param name="newOwner">The public key of the account to transfer ownership to.</param>
/// <returns>The transaction instruction data.</returns>
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;
}

Expand All @@ -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;
}
}
Expand Down
105 changes: 53 additions & 52 deletions src/Solnet.Programs/SharedMemoryProgram.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Helper class for the Shared Memory Program.
/// <remarks>
/// Used to write data to a given account data.
/// Note: this programm, as of writting this, was inactive in some clusters.
/// </remarks>
/// </summary>
public static class SharedMemoryProgram
{
/// <summary>
/// The address of the Shared Memory Program.
/// </summary>
public static readonly PublicKey ProgramId = new("shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL");

/// <summary>
/// Creates an instruction used to interact with the Shared memory program.
/// This instruction writes data to a given program starting at a specific offset.
/// </summary>
/// <param name="dest">The account where the data is to be written.</param>
/// <param name="payload">The data to be written.</param>
/// <param name="offset">The offset of the account data to write to.</param>
/// <returns>The <see cref="TransactionInstruction"/> encoded that interacts with the shared memory program..</returns>
public static TransactionInstruction Write(PublicKey dest, byte[] payload, ulong offset)
{
var keys = new List<AccountMeta>
{
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
{
/// <summary>
/// Helper class for the Shared Memory Program.
/// <remarks>
/// Used to write data to a given account data.
/// Note: this program, as of writing this, was inactive in some clusters.
/// </remarks>
/// </summary>
public static class SharedMemoryProgram
{
/// <summary>
/// The address of the Shared Memory Program.
/// </summary>
public static readonly PublicKey ProgramId = new("shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL");

/// <summary>
/// Creates an instruction used to interact with the Shared memory program.
/// This instruction writes data to a given program starting at a specific offset.
/// </summary>
/// <param name="dest">The account where the data is to be written.</param>
/// <param name="payload">The data to be written.</param>
/// <param name="offset">The offset of the account data to write to.</param>
/// <returns>The <see cref="TransactionInstruction"/> encoded that interacts with the shared memory program..</returns>
public static TransactionInstruction Write(PublicKey dest, ReadOnlySpan<byte> payload, ulong offset)
{
List<AccountMeta> 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
};
}

}
}
Loading

0 comments on commit c24a32a

Please sign in to comment.