Skip to content

Commit 2340627

Browse files
authored
Merge pull request #419 from BifrostTitan/master
Added compute budget program, priority fees and versioned transactions. Bug fixes and more
2 parents 4f85bac + 9a1f26e commit 2340627

33 files changed

+763
-57
lines changed

src/Solnet.Examples/Solnet.Examples.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net6.0</TargetFramework>
5+
<TargetFramework>net8.0</TargetFramework>
66
<OutputType>Exe</OutputType>
77
<IsPackable>false</IsPackable>
88
</PropertyGroup>

src/Solnet.Extensions/Solnet.Extensions.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net8.0</TargetFramework>
55
</PropertyGroup>
66

77
<ItemGroup>

src/Solnet.KeyStore/Solnet.KeyStore.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net8.0</TargetFramework>
55
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
66
</PropertyGroup>
77

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using Solnet.Programs.Utilities;
2+
using Solnet.Rpc.Models;
3+
using Solnet.Wallet;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace Solnet.Programs
11+
{
12+
/// <summary>
13+
/// Implements the ComputeBudget Program methods.
14+
/// <remarks>
15+
/// For more information see: https://spl.solana.com/memo
16+
/// </remarks>
17+
/// </summary>
18+
19+
public class ComputeBudgetProgram
20+
{
21+
22+
/// <summary>
23+
/// The public key of the ComputeBudget Program.
24+
/// </summary>
25+
public static readonly PublicKey ProgramIdKey = new("ComputeBudget111111111111111111111111111111");
26+
27+
28+
/// <summary>
29+
/// The program's name.
30+
/// </summary>
31+
private const string ProgramName = "Compute Budget Program";
32+
33+
34+
35+
/// <summary>
36+
/// Request HeapFrame Instruction related to Priority Fees
37+
/// </summary>
38+
/// <param name="bytes"></param>
39+
/// <returns></returns>
40+
public static TransactionInstruction RequestHeapFrame(uint bytes)
41+
{
42+
List<AccountMeta> keys = new();
43+
44+
byte[] instructionBytes = new byte[17];
45+
instructionBytes.WriteU8(1, 0);
46+
instructionBytes.WriteU32(bytes, 1);
47+
48+
return new TransactionInstruction
49+
{
50+
ProgramId = ProgramIdKey.KeyBytes,
51+
Keys = keys,
52+
Data = instructionBytes
53+
};
54+
}
55+
/// <summary>
56+
/// Set Compute Unit Limit Instruction for Priority Fees
57+
/// </summary>
58+
/// <param name="units"></param>
59+
/// <returns></returns>
60+
public static TransactionInstruction SetComputeUnitLimit(uint units)
61+
{
62+
List<AccountMeta> keys = new();
63+
64+
byte[] instructionBytes = new byte[9];
65+
instructionBytes.WriteU8(2, 0);
66+
instructionBytes.WriteU64(units, 1);
67+
68+
return new TransactionInstruction
69+
{
70+
ProgramId = ProgramIdKey.KeyBytes,
71+
Keys = keys,
72+
Data = instructionBytes
73+
};
74+
}
75+
/// <summary>
76+
/// Set Compute Unit Price Instruction for Priority Fees
77+
/// </summary>
78+
/// <param name="priority_rate"></param>
79+
/// <returns></returns>
80+
public static TransactionInstruction SetComputeUnitPrice(ulong priority_rate)
81+
{
82+
List<AccountMeta> keys = new();
83+
84+
byte[] instructionBytes = new byte[9];
85+
instructionBytes.WriteU8(3, 0);
86+
instructionBytes.WriteU64(priority_rate, 1);
87+
88+
return new TransactionInstruction
89+
{
90+
ProgramId = ProgramIdKey.KeyBytes,
91+
Keys = keys,
92+
Data = instructionBytes
93+
};
94+
}
95+
96+
}
97+
}

src/Solnet.Programs/Solnet.Programs.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
4+
<TargetFramework>net8.0</TargetFramework>
55
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
66
</PropertyGroup>
77

src/Solnet.Programs/TokenProgramData.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,10 @@ internal static void DecodeSetAuthorityData(DecodedInstruction decodedInstructio
302302
decodedInstruction.Values.Add("Current Authority", keys[keyIndices[1]]);
303303
decodedInstruction.Values.Add("Authority Type", Enum.Parse(typeof(AuthorityType), data.GetU8(1).ToString()));
304304
decodedInstruction.Values.Add("New Authority Option", data.GetU8(2));
305-
decodedInstruction.Values.Add("New Authority", data.GetPubKey(3));
305+
if (data.Length >= 34)
306+
{
307+
decodedInstruction.Values.Add("New Authority", data.GetPubKey(3));
308+
}
306309
for (int i = 2; i < keyIndices.Length; i++)
307310
{
308311
decodedInstruction.Values.Add($"Signer {i - 1}", keys[keyIndices[i]]);

src/Solnet.Rpc/Builders/MessageBuilder.cs

+9-10
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,22 @@ public class MessageBuilder
1818
/// <summary>
1919
/// The length of the block hash.
2020
/// </summary>
21-
private const int BlockHashLength = 32;
21+
protected const int BlockHashLength = 32;
2222

2323
/// <summary>
2424
/// The message header.
2525
/// </summary>
26-
private MessageHeader _messageHeader;
26+
protected MessageHeader _messageHeader;
2727

2828
/// <summary>
2929
/// The account keys list.
3030
/// </summary>
31-
private readonly AccountKeysList _accountKeysList;
31+
protected readonly AccountKeysList _accountKeysList;
3232

3333
/// <summary>
3434
/// The list of instructions contained within this transaction.
3535
/// </summary>
36-
internal List<TransactionInstruction> Instructions { get; private set; }
36+
internal List<TransactionInstruction> Instructions { get; private protected set; }
3737

3838
/// <summary>
3939
/// The hash of a recent block.
@@ -76,7 +76,7 @@ internal MessageBuilder AddInstruction(TransactionInstruction instruction)
7676
/// Builds the message into the wire format.
7777
/// </summary>
7878
/// <returns>The encoded message.</returns>
79-
internal byte[] Build()
79+
internal virtual byte[] Build()
8080
{
8181
if (RecentBlockHash == null && NonceInformation == null)
8282
throw new Exception("recent block hash or nonce information is required");
@@ -111,8 +111,7 @@ internal byte[] Build()
111111
{
112112
keyIndices[i] = FindAccountIndex(keysList, instruction.Keys[i].PublicKey);
113113
}
114-
115-
CompiledInstruction compiledInstruction = new CompiledInstruction
114+
CompiledInstruction compiledInstruction = new()
116115
{
117116
ProgramIdIndex = FindAccountIndex(keysList, instruction.ProgramId),
118117
KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyCount),
@@ -176,7 +175,7 @@ internal byte[] Build()
176175
/// Gets the keys for the accounts present in the message.
177176
/// </summary>
178177
/// <returns>The list of <see cref="AccountMeta"/>.</returns>
179-
private List<AccountMeta> GetAccountKeys()
178+
protected List<AccountMeta> GetAccountKeys()
180179
{
181180
List<AccountMeta> newList = new();
182181
var keysList = _accountKeysList.AccountList;
@@ -203,7 +202,7 @@ private List<AccountMeta> GetAccountKeys()
203202
/// <param name="accountMetas">The <see cref="AccountMeta"/>.</param>
204203
/// <param name="publicKey">The public key.</param>
205204
/// <returns>The index of the</returns>
206-
private static byte FindAccountIndex(IList<AccountMeta> accountMetas, byte[] publicKey)
205+
protected static byte FindAccountIndex(IList<AccountMeta> accountMetas, byte[] publicKey)
207206
{
208207
string encodedKey = Encoders.Base58.EncodeData(publicKey);
209208
return FindAccountIndex(accountMetas, encodedKey);
@@ -215,7 +214,7 @@ private static byte FindAccountIndex(IList<AccountMeta> accountMetas, byte[] pub
215214
/// <param name="accountMetas">The <see cref="AccountMeta"/>.</param>
216215
/// <param name="publicKey">The public key.</param>
217216
/// <returns>The index of the</returns>
218-
private static byte FindAccountIndex(IList<AccountMeta> accountMetas, string publicKey)
217+
protected static byte FindAccountIndex(IList<AccountMeta> accountMetas, string publicKey)
219218
{
220219
for (byte index = 0; index < accountMetas.Count; index++)
221220
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using Solnet.Rpc.Models;
2+
using Solnet.Rpc.Utilities;
3+
using Solnet.Wallet;
4+
using Solnet.Wallet.Utilities;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using static Solnet.Rpc.Models.Message;
10+
11+
namespace Solnet.Rpc.Builders
12+
{
13+
/// <summary>
14+
/// A compiled instruction within the message.
15+
/// </summary>
16+
public class VersionedMessageBuilder : MessageBuilder
17+
{
18+
19+
/// <summary>
20+
/// Address Table Lookups
21+
/// </summary>
22+
public List<MessageAddressTableLookup> AddressTableLookups { get; set; }
23+
public IList<PublicKey> AccountKeys { get; internal set; }
24+
25+
/// <summary>
26+
/// Builds the message into the wire format.
27+
/// </summary>
28+
/// <returns>The encoded message.</returns>
29+
internal override byte[] Build()
30+
{
31+
if (RecentBlockHash == null && NonceInformation == null)
32+
throw new Exception("recent block hash or nonce information is required");
33+
if (Instructions == null)
34+
throw new Exception("no instructions provided in the transaction");
35+
36+
// In case the user specified nonce information, we'll use it.
37+
if (NonceInformation != null)
38+
{
39+
RecentBlockHash = NonceInformation.Nonce;
40+
_accountKeysList.Add(NonceInformation.Instruction.Keys);
41+
_accountKeysList.Add(AccountMeta.ReadOnly(new PublicKey(NonceInformation.Instruction.ProgramId),
42+
false));
43+
List<TransactionInstruction> newInstructions = new() { NonceInformation.Instruction };
44+
newInstructions.AddRange(Instructions);
45+
Instructions = newInstructions;
46+
}
47+
48+
_messageHeader = new MessageHeader();
49+
50+
List<AccountMeta> keysList = GetAccountKeys();
51+
byte[] accountAddressesLength = ShortVectorEncoding.EncodeLength(keysList.Count);
52+
int compiledInstructionsLength = 0;
53+
List<CompiledInstruction> compiledInstructions = new();
54+
55+
foreach (TransactionInstruction instruction in Instructions)
56+
{
57+
int keyCount = instruction.Keys.Count;
58+
byte[] keyIndices = new byte[keyCount];
59+
60+
if (instruction.GetType() == typeof(VersionedTransactionInstruction))
61+
{
62+
keyIndices = ((VersionedTransactionInstruction)instruction).KeyIndices;
63+
}
64+
else
65+
{
66+
for (int i = 0; i < keyCount; i++)
67+
{
68+
keyIndices[i] = FindAccountIndex(keysList, instruction.Keys[i].PublicKey);
69+
}
70+
}
71+
72+
CompiledInstruction compiledInstruction = new()
73+
{
74+
ProgramIdIndex = FindAccountIndex(keysList, instruction.ProgramId),
75+
KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyIndices.Length),
76+
KeyIndices = keyIndices,
77+
DataLength = ShortVectorEncoding.EncodeLength(instruction.Data.Length),
78+
Data = instruction.Data
79+
};
80+
compiledInstructions.Add(compiledInstruction);
81+
compiledInstructionsLength += compiledInstruction.Length();
82+
}
83+
84+
int accountKeysBufferSize = _accountKeysList.AccountList.Count * 32;
85+
MemoryStream accountKeysBuffer = new MemoryStream(accountKeysBufferSize);
86+
byte[] instructionsLength = ShortVectorEncoding.EncodeLength(compiledInstructions.Count);
87+
88+
foreach (AccountMeta accountMeta in keysList)
89+
{
90+
accountKeysBuffer.Write(accountMeta.PublicKeyBytes, 0, accountMeta.PublicKeyBytes.Length);
91+
if (accountMeta.IsSigner)
92+
{
93+
_messageHeader.RequiredSignatures += 1;
94+
if (!accountMeta.IsWritable)
95+
_messageHeader.ReadOnlySignedAccounts += 1;
96+
}
97+
else
98+
{
99+
if (!accountMeta.IsWritable)
100+
_messageHeader.ReadOnlyUnsignedAccounts += 1;
101+
}
102+
}
103+
104+
#region Build Message Body
105+
106+
int messageBufferSize = MessageHeader.Layout.HeaderLength + BlockHashLength +
107+
accountAddressesLength.Length +
108+
+instructionsLength.Length + compiledInstructionsLength + accountKeysBufferSize;
109+
MemoryStream buffer = new MemoryStream(messageBufferSize);
110+
byte[] messageHeaderBytes = _messageHeader.ToBytes();
111+
112+
buffer.Write(new byte[] { 128 }, 0, 1);
113+
buffer.Write(messageHeaderBytes, 0, messageHeaderBytes.Length);
114+
buffer.Write(accountAddressesLength, 0, accountAddressesLength.Length);
115+
buffer.Write(accountKeysBuffer.ToArray(), 0, accountKeysBuffer.ToArray().Length);
116+
var encodedRecentBlockHash = Encoders.Base58.DecodeData(RecentBlockHash);
117+
buffer.Write(encodedRecentBlockHash, 0, encodedRecentBlockHash.Length);
118+
buffer.Write(instructionsLength, 0, instructionsLength.Length);
119+
120+
foreach (CompiledInstruction compiledInstruction in compiledInstructions)
121+
{
122+
buffer.WriteByte(compiledInstruction.ProgramIdIndex);
123+
buffer.Write(compiledInstruction.KeyIndicesCount, 0, compiledInstruction.KeyIndicesCount.Length);
124+
buffer.Write(compiledInstruction.KeyIndices, 0, compiledInstruction.KeyIndices.Length);
125+
buffer.Write(compiledInstruction.DataLength, 0, compiledInstruction.DataLength.Length);
126+
buffer.Write(compiledInstruction.Data, 0, compiledInstruction.Data.Length);
127+
}
128+
129+
#endregion
130+
131+
var serializeAddressTableLookups = AddressTableLookupUtils.SerializeAddressTableLookups(AddressTableLookups);
132+
buffer.Write(serializeAddressTableLookups, 0, serializeAddressTableLookups.Length);
133+
134+
return buffer.ToArray();
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)