Skip to content

Commit

Permalink
Merge pull request #419 from BifrostTitan/master
Browse files Browse the repository at this point in the history
Added compute budget program, priority fees and versioned transactions. Bug fixes and more
  • Loading branch information
BifrostTitan authored Apr 15, 2024
2 parents 4f85bac + 9a1f26e commit 2340627
Show file tree
Hide file tree
Showing 33 changed files with 763 additions and 57 deletions.
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

0 comments on commit 2340627

Please sign in to comment.