Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added compute budget program, priority fees and versioned transactions. Bug fixes and more #419

Merged
merged 12 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Solnet.Examples/Solnet.Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Solnet.Extensions/Solnet.Extensions.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Solnet.KeyStore/Solnet.KeyStore.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Expand Down
97 changes: 97 additions & 0 deletions src/Solnet.Programs/ComputeBudgetProgram.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using Solnet.Programs.Utilities;
using Solnet.Rpc.Models;
using Solnet.Wallet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Solnet.Programs
{
/// <summary>
/// Implements the ComputeBudget Program methods.
/// <remarks>
/// For more information see: https://spl.solana.com/memo
/// </remarks>
/// </summary>

public class ComputeBudgetProgram
{

/// <summary>
/// The public key of the ComputeBudget Program.
/// </summary>
public static readonly PublicKey ProgramIdKey = new("ComputeBudget111111111111111111111111111111");


/// <summary>
/// The program's name.
/// </summary>
private const string ProgramName = "Compute Budget Program";



/// <summary>
/// Request HeapFrame Instruction related to Priority Fees
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static TransactionInstruction RequestHeapFrame(uint bytes)
{
List<AccountMeta> keys = new();

byte[] instructionBytes = new byte[17];
instructionBytes.WriteU8(1, 0);
instructionBytes.WriteU32(bytes, 1);

return new TransactionInstruction
{
ProgramId = ProgramIdKey.KeyBytes,
Keys = keys,
Data = instructionBytes
};
}
/// <summary>
/// Set Compute Unit Limit Instruction for Priority Fees
/// </summary>
/// <param name="units"></param>
/// <returns></returns>
public static TransactionInstruction SetComputeUnitLimit(uint units)
{
List<AccountMeta> keys = new();

byte[] instructionBytes = new byte[9];
instructionBytes.WriteU8(2, 0);
instructionBytes.WriteU64(units, 1);

return new TransactionInstruction
{
ProgramId = ProgramIdKey.KeyBytes,
Keys = keys,
Data = instructionBytes
};
}
/// <summary>
/// Set Compute Unit Price Instruction for Priority Fees
/// </summary>
/// <param name="priority_rate"></param>
/// <returns></returns>
public static TransactionInstruction SetComputeUnitPrice(ulong priority_rate)
{
List<AccountMeta> keys = new();

byte[] instructionBytes = new byte[9];
instructionBytes.WriteU8(3, 0);
instructionBytes.WriteU64(priority_rate, 1);

return new TransactionInstruction
{
ProgramId = ProgramIdKey.KeyBytes,
Keys = keys,
Data = instructionBytes
};
}

}
}
2 changes: 1 addition & 1 deletion src/Solnet.Programs/Solnet.Programs.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Expand Down
5 changes: 4 additions & 1 deletion src/Solnet.Programs/TokenProgramData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,10 @@ internal static void DecodeSetAuthorityData(DecodedInstruction decodedInstructio
decodedInstruction.Values.Add("Current Authority", keys[keyIndices[1]]);
decodedInstruction.Values.Add("Authority Type", Enum.Parse(typeof(AuthorityType), data.GetU8(1).ToString()));
decodedInstruction.Values.Add("New Authority Option", data.GetU8(2));
decodedInstruction.Values.Add("New Authority", data.GetPubKey(3));
if (data.Length >= 34)
{
decodedInstruction.Values.Add("New Authority", data.GetPubKey(3));
}
for (int i = 2; i < keyIndices.Length; i++)
{
decodedInstruction.Values.Add($"Signer {i - 1}", keys[keyIndices[i]]);
Expand Down
19 changes: 9 additions & 10 deletions src/Solnet.Rpc/Builders/MessageBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ public class MessageBuilder
/// <summary>
/// The length of the block hash.
/// </summary>
private const int BlockHashLength = 32;
protected const int BlockHashLength = 32;

/// <summary>
/// The message header.
/// </summary>
private MessageHeader _messageHeader;
protected MessageHeader _messageHeader;

/// <summary>
/// The account keys list.
/// </summary>
private readonly AccountKeysList _accountKeysList;
protected readonly AccountKeysList _accountKeysList;

/// <summary>
/// The list of instructions contained within this transaction.
/// </summary>
internal List<TransactionInstruction> Instructions { get; private set; }
internal List<TransactionInstruction> Instructions { get; private protected set; }

/// <summary>
/// The hash of a recent block.
Expand Down Expand Up @@ -76,7 +76,7 @@ internal MessageBuilder AddInstruction(TransactionInstruction instruction)
/// Builds the message into the wire format.
/// </summary>
/// <returns>The encoded message.</returns>
internal byte[] Build()
internal virtual byte[] Build()
{
if (RecentBlockHash == null && NonceInformation == null)
throw new Exception("recent block hash or nonce information is required");
Expand Down Expand Up @@ -111,8 +111,7 @@ internal byte[] Build()
{
keyIndices[i] = FindAccountIndex(keysList, instruction.Keys[i].PublicKey);
}

CompiledInstruction compiledInstruction = new CompiledInstruction
CompiledInstruction compiledInstruction = new()
{
ProgramIdIndex = FindAccountIndex(keysList, instruction.ProgramId),
KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyCount),
Expand Down Expand Up @@ -176,7 +175,7 @@ internal byte[] Build()
/// Gets the keys for the accounts present in the message.
/// </summary>
/// <returns>The list of <see cref="AccountMeta"/>.</returns>
private List<AccountMeta> GetAccountKeys()
protected List<AccountMeta> GetAccountKeys()
{
List<AccountMeta> newList = new();
var keysList = _accountKeysList.AccountList;
Expand All @@ -203,7 +202,7 @@ private List<AccountMeta> GetAccountKeys()
/// <param name="accountMetas">The <see cref="AccountMeta"/>.</param>
/// <param name="publicKey">The public key.</param>
/// <returns>The index of the</returns>
private static byte FindAccountIndex(IList<AccountMeta> accountMetas, byte[] publicKey)
protected static byte FindAccountIndex(IList<AccountMeta> accountMetas, byte[] publicKey)
{
string encodedKey = Encoders.Base58.EncodeData(publicKey);
return FindAccountIndex(accountMetas, encodedKey);
Expand All @@ -215,7 +214,7 @@ private static byte FindAccountIndex(IList<AccountMeta> accountMetas, byte[] pub
/// <param name="accountMetas">The <see cref="AccountMeta"/>.</param>
/// <param name="publicKey">The public key.</param>
/// <returns>The index of the</returns>
private static byte FindAccountIndex(IList<AccountMeta> accountMetas, string publicKey)
protected static byte FindAccountIndex(IList<AccountMeta> accountMetas, string publicKey)
{
for (byte index = 0; index < accountMetas.Count; index++)
{
Expand Down
137 changes: 137 additions & 0 deletions src/Solnet.Rpc/Builders/VersionedMessageBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using Solnet.Rpc.Models;
using Solnet.Rpc.Utilities;
using Solnet.Wallet;
using Solnet.Wallet.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static Solnet.Rpc.Models.Message;

namespace Solnet.Rpc.Builders
{
/// <summary>
/// A compiled instruction within the message.
/// </summary>
public class VersionedMessageBuilder : MessageBuilder
{

/// <summary>
/// Address Table Lookups
/// </summary>
public List<MessageAddressTableLookup> AddressTableLookups { get; set; }
public IList<PublicKey> AccountKeys { get; internal set; }

/// <summary>
/// Builds the message into the wire format.
/// </summary>
/// <returns>The encoded message.</returns>
internal override byte[] Build()
{
if (RecentBlockHash == null && NonceInformation == null)
throw new Exception("recent block hash or nonce information is required");
if (Instructions == null)
throw new Exception("no instructions provided in the transaction");

// In case the user specified nonce information, we'll use it.
if (NonceInformation != null)
{
RecentBlockHash = NonceInformation.Nonce;
_accountKeysList.Add(NonceInformation.Instruction.Keys);
_accountKeysList.Add(AccountMeta.ReadOnly(new PublicKey(NonceInformation.Instruction.ProgramId),
false));
List<TransactionInstruction> newInstructions = new() { NonceInformation.Instruction };
newInstructions.AddRange(Instructions);
Instructions = newInstructions;
}

_messageHeader = new MessageHeader();

List<AccountMeta> keysList = GetAccountKeys();
byte[] accountAddressesLength = ShortVectorEncoding.EncodeLength(keysList.Count);
int compiledInstructionsLength = 0;
List<CompiledInstruction> compiledInstructions = new();

foreach (TransactionInstruction instruction in Instructions)
{
int keyCount = instruction.Keys.Count;
byte[] keyIndices = new byte[keyCount];

if (instruction.GetType() == typeof(VersionedTransactionInstruction))
{
keyIndices = ((VersionedTransactionInstruction)instruction).KeyIndices;
}
else
{
for (int i = 0; i < keyCount; i++)
{
keyIndices[i] = FindAccountIndex(keysList, instruction.Keys[i].PublicKey);
}
}

CompiledInstruction compiledInstruction = new()
{
ProgramIdIndex = FindAccountIndex(keysList, instruction.ProgramId),
KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyIndices.Length),
KeyIndices = keyIndices,
DataLength = ShortVectorEncoding.EncodeLength(instruction.Data.Length),
Data = instruction.Data
};
compiledInstructions.Add(compiledInstruction);
compiledInstructionsLength += compiledInstruction.Length();
}

int accountKeysBufferSize = _accountKeysList.AccountList.Count * 32;
MemoryStream accountKeysBuffer = new MemoryStream(accountKeysBufferSize);
byte[] instructionsLength = ShortVectorEncoding.EncodeLength(compiledInstructions.Count);

foreach (AccountMeta accountMeta in keysList)
{
accountKeysBuffer.Write(accountMeta.PublicKeyBytes, 0, accountMeta.PublicKeyBytes.Length);
if (accountMeta.IsSigner)
{
_messageHeader.RequiredSignatures += 1;
if (!accountMeta.IsWritable)
_messageHeader.ReadOnlySignedAccounts += 1;
}
else
{
if (!accountMeta.IsWritable)
_messageHeader.ReadOnlyUnsignedAccounts += 1;
}
}

#region Build Message Body

int messageBufferSize = MessageHeader.Layout.HeaderLength + BlockHashLength +
accountAddressesLength.Length +
+instructionsLength.Length + compiledInstructionsLength + accountKeysBufferSize;
MemoryStream buffer = new MemoryStream(messageBufferSize);
byte[] messageHeaderBytes = _messageHeader.ToBytes();

buffer.Write(new byte[] { 128 }, 0, 1);
buffer.Write(messageHeaderBytes, 0, messageHeaderBytes.Length);
buffer.Write(accountAddressesLength, 0, accountAddressesLength.Length);
buffer.Write(accountKeysBuffer.ToArray(), 0, accountKeysBuffer.ToArray().Length);
var encodedRecentBlockHash = Encoders.Base58.DecodeData(RecentBlockHash);
buffer.Write(encodedRecentBlockHash, 0, encodedRecentBlockHash.Length);
buffer.Write(instructionsLength, 0, instructionsLength.Length);

foreach (CompiledInstruction compiledInstruction in compiledInstructions)
{
buffer.WriteByte(compiledInstruction.ProgramIdIndex);
buffer.Write(compiledInstruction.KeyIndicesCount, 0, compiledInstruction.KeyIndicesCount.Length);
buffer.Write(compiledInstruction.KeyIndices, 0, compiledInstruction.KeyIndices.Length);
buffer.Write(compiledInstruction.DataLength, 0, compiledInstruction.DataLength.Length);
buffer.Write(compiledInstruction.Data, 0, compiledInstruction.Data.Length);
}

#endregion

var serializeAddressTableLookups = AddressTableLookupUtils.SerializeAddressTableLookups(AddressTableLookups);
buffer.Write(serializeAddressTableLookups, 0, serializeAddressTableLookups.Length);

return buffer.ToArray();
}
}
}
Loading
Loading