Skip to content

Commit 884de96

Browse files
authored
Merge pull request #362 from Neo-vortex/Add_BPFLoaderProgram
Add BPF Loader Program. Minus 1 commit with changes to deserialization
2 parents 33173c8 + cf62860 commit 884de96

File tree

8 files changed

+385
-8
lines changed

8 files changed

+385
-8
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
using Solnet.Rpc.Models;
2+
using Solnet.Wallet;
3+
using System;
4+
using System.Collections.Generic;
5+
6+
namespace Solnet.Programs
7+
{
8+
/// <summary>
9+
/// Implements the BPF Loader Program methods.
10+
/// <remarks>
11+
/// For more information see:
12+
/// https://docs.rs/solana-sdk/1.9.13/solana_sdk/loader_upgradeable_instruction/enum.UpgradeableLoaderInstruction.html
13+
/// </remarks>
14+
/// </summary>
15+
public static class BPFLoaderProgram
16+
{
17+
/// <summary>
18+
/// The public key of the BPF Loader Program.
19+
/// </summary>
20+
public static readonly PublicKey ProgramIdKey = new("BPFLoaderUpgradeab1e11111111111111111111111");
21+
/// <summary>
22+
/// The program's name.
23+
/// </summary>
24+
private const string ProgramName = "BPF Loader Program";
25+
/// <summary>
26+
/// Initialize a Buffer account.
27+
/// A Buffer account is an intermediary that once fully populated is used with the DeployWithMaxDataLen instruction to populate the program’s ProgramData account.
28+
/// The InitializeBuffer instruction requires no signers and MUST be included within the same Transaction as the system program’s CreateAccount instruction that creates the account being initialized. Otherwise another party may initialize the account.
29+
/// </summary>
30+
/// <param name="sourceAccount">public key of the account to init</param>
31+
/// <param name="authority">public key of the authority over the account</param>
32+
/// <returns>The transaction instruction</returns>
33+
public static TransactionInstruction InitializeBuffer(PublicKey sourceAccount , PublicKey authority = null)
34+
{
35+
var keys = new List<AccountMeta> ()
36+
{
37+
AccountMeta.Writable(sourceAccount, false),
38+
};
39+
if (authority !=null) keys.Add( AccountMeta.ReadOnly(authority,false));
40+
return new TransactionInstruction
41+
{
42+
ProgramId = ProgramIdKey.KeyBytes,
43+
Keys = keys,
44+
Data = BPFLoaderProgramData.EncodeInitializeBuffer().ToArray()
45+
};
46+
}
47+
48+
/// <summary>
49+
/// Write program data into a Buffer account.
50+
/// </summary>
51+
/// <param name="bufferAccount">the public key of the buffer account</param>
52+
/// <param name="bufferAuthority">the public key of the authority over the buffer account</param>
53+
/// <param name="data">data to write to the buffer account (Serialized program data)</param>
54+
/// <param name="offset">offset at which to write the given data.</param>
55+
/// <returns>The transaction instruction</returns>
56+
public static TransactionInstruction Write(PublicKey bufferAccount, PublicKey bufferAuthority, Span<byte> data, uint offset)
57+
{
58+
var keys = new List<AccountMeta> ()
59+
{
60+
AccountMeta.Writable(bufferAccount, false),
61+
AccountMeta.ReadOnly(bufferAuthority, true),
62+
};
63+
return new TransactionInstruction
64+
{
65+
ProgramId = ProgramIdKey.KeyBytes,
66+
Keys = keys,
67+
Data = BPFLoaderProgramData.EncodeWrite(offset, data).ToArray()
68+
};
69+
}
70+
/// <summary>
71+
///A program consists of a Program and ProgramData account pair
72+
/// The Program account’s address will serve as the program id for any instructions that execute this program.
73+
/// The ProgramData account will remain mutable by the loader only and holds the program data and authority information. The ProgramData account’s address is derived from the Program account’s address and created by the DeployWithMaxDataLen instruction
74+
/// </summary>
75+
/// <param name="payer">The payer account that will pay to create the ProgramData account</param>
76+
/// <param name="programDataAccount">The uninitialized ProgramData account</param>
77+
/// <param name="programAccount">The uninitialized Program account</param>
78+
/// <param name="bufferAccount"> The Buffer account where the program data has been written. The buffer account’s authority must match the program’s authority</param>
79+
/// <param name="authority">the public key of the authority</param>
80+
/// <param name="maxDataLenght">Maximum length that the program can be upgraded to</param>
81+
/// <returns>The transaction instruction</returns>
82+
public static TransactionInstruction DeployWithMaxDataLen(PublicKey payer, PublicKey programDataAccount,
83+
PublicKey programAccount, PublicKey bufferAccount, PublicKey authority, ulong maxDataLenght)
84+
{
85+
var keys = new List<AccountMeta> ()
86+
{
87+
AccountMeta.ReadOnly(payer, true),
88+
AccountMeta.Writable(programDataAccount, false),
89+
AccountMeta.Writable(programAccount, false),
90+
AccountMeta.Writable(bufferAccount, false),
91+
AccountMeta.ReadOnly(SysVars.RentKey, false),
92+
AccountMeta.ReadOnly(SysVars.ClockKey, false),
93+
AccountMeta.ReadOnly(SystemProgram.ProgramIdKey, false),
94+
AccountMeta.ReadOnly(authority, true),
95+
};
96+
return new TransactionInstruction
97+
{
98+
ProgramId = ProgramIdKey.KeyBytes,
99+
Keys = keys,
100+
Data = BPFLoaderProgramData.EncodeDeployWithMaxDataLen(maxDataLenght).ToArray()
101+
};
102+
}
103+
/// <summary>
104+
/// Upgrade a program
105+
///A program can be updated as long as the program’s authority has not been set to None.
106+
/// The Buffer account must contain sufficient lamports to fund the ProgramData account to be rent-exempt, any additional lamports left over will be transferred to the spill account, leaving the Buffer account balance at zero.
107+
/// </summary>
108+
/// <param name="programDataAccount">The ProgramData account.</param>
109+
/// <param name="programAccount"></param>
110+
/// <param name="bufferAccount"></param>
111+
/// <param name="spillAccount"></param>
112+
/// <param name="authority"></param>
113+
/// <returns>The transaction instruction</returns>
114+
public static TransactionInstruction Upgrade(PublicKey programDataAccount, PublicKey programAccount,
115+
PublicKey bufferAccount, PublicKey spillAccount, PublicKey authority)
116+
{
117+
var keys = new List<AccountMeta> ()
118+
{
119+
AccountMeta.Writable(programDataAccount, false),
120+
AccountMeta.Writable(programAccount, false),
121+
AccountMeta.Writable(bufferAccount, false),
122+
AccountMeta.Writable(spillAccount, false),
123+
AccountMeta.ReadOnly(SysVars.RentKey, false),
124+
AccountMeta.ReadOnly(SysVars.ClockKey, false),
125+
AccountMeta.ReadOnly(authority, true),
126+
};
127+
return new TransactionInstruction
128+
{
129+
ProgramId = ProgramIdKey.KeyBytes,
130+
Keys = keys,
131+
Data = BPFLoaderProgramData.EncodeUpgrade().ToArray()
132+
};
133+
}
134+
135+
/// <summary>
136+
///Set a new authority that is allowed to write the buffer or upgrade the program. To permanently make the buffer immutable or disable program updates omit the new authority.
137+
/// </summary>
138+
/// <param name="bufferOrProgramDataaccount"></param>
139+
/// <param name="authority"></param>
140+
/// <param name="newAuthority"></param>
141+
/// <returns>The transaction instruction</returns>
142+
public static TransactionInstruction SetAuthority(PublicKey bufferOrProgramDataaccount, PublicKey authority,
143+
PublicKey newAuthority = null)
144+
{
145+
var keys = new List<AccountMeta> ()
146+
{
147+
AccountMeta.Writable(bufferOrProgramDataaccount, false),
148+
AccountMeta.ReadOnly(authority, true),
149+
};
150+
if (newAuthority != null) keys.Add(AccountMeta.ReadOnly(newAuthority,false));
151+
return new TransactionInstruction
152+
{
153+
ProgramId = ProgramIdKey.KeyBytes,
154+
Keys = keys,
155+
Data = BPFLoaderProgramData.EncodeSetAuthority().ToArray()
156+
};
157+
}
158+
/// <summary>
159+
/// Closes an account owned by the upgradeable loader of all lamports and withdraws all the lamports
160+
/// </summary>
161+
/// <param name="accountToClose">public key of the account to close</param>
162+
/// <param name="depositAccount">public key of the account to transfer remaining lamports to </param>
163+
/// <param name="associatedProgramAccount">public key of the associated program account</param>
164+
/// <param name="authority">public key of the authority for the account to close</param>
165+
/// <returns>The transaction instruction</returns>
166+
public static TransactionInstruction Close(PublicKey accountToClose, PublicKey depositAccount,
167+
PublicKey associatedProgramAccount = null, PublicKey authority = null)
168+
{
169+
var keys = new List<AccountMeta> ()
170+
{
171+
AccountMeta.Writable(accountToClose, false),
172+
AccountMeta.Writable(depositAccount, false),
173+
};
174+
if (authority != null) keys.Add(AccountMeta.ReadOnly(authority, true));
175+
if (associatedProgramAccount != null) keys.Add(AccountMeta.Writable(associatedProgramAccount,false));
176+
return new TransactionInstruction
177+
{
178+
ProgramId = ProgramIdKey.KeyBytes,
179+
Keys = keys,
180+
Data = BPFLoaderProgramData.EncodeClose().ToArray()
181+
};
182+
}
183+
}
184+
}
185+
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using Solnet.Programs.Utilities;
2+
using System;
3+
4+
namespace Solnet.Programs;
5+
6+
public class BPFLoaderProgramData
7+
{
8+
/// <summary>
9+
/// Method offset is zero for this program
10+
/// </summary>
11+
private const int MethodOffset = 0;
12+
/// <summary>
13+
/// Encode Write data
14+
/// </summary>
15+
/// <param name="offset">the offset</param>
16+
/// <param name="buffer">buffer to be written</param>
17+
/// <returns>The encoded data</returns>
18+
internal static Span<byte> EncodeWrite(uint offset, Span<byte> buffer)
19+
{
20+
byte[] data = new byte[sizeof(uint) + sizeof(uint) +sizeof(ulong) + buffer.Length];
21+
data.WriteU32( (uint) BPFLoaderProgramInstructions.Values.Write, MethodOffset);
22+
data.WriteU32( offset, MethodOffset + sizeof(uint));
23+
data.WriteBorshByteVector(buffer, sizeof(uint) + sizeof(uint) + MethodOffset);
24+
return data;
25+
}
26+
/// <summary>
27+
/// Encode InitializeBuffer data
28+
/// </summary>
29+
/// <returns>The encoded data</returns>
30+
internal static Span<byte> EncodeInitializeBuffer()
31+
{
32+
byte[] data = new byte[sizeof(uint)];
33+
data.WriteU32((uint)BPFLoaderProgramInstructions.Values.InitializeBuffer, MethodOffset);
34+
return data;
35+
}
36+
/// <summary>
37+
/// Encode DeployWithMaxDataLen data
38+
/// </summary>
39+
/// <param name="maxDataLenght">the max data lenght</param>
40+
/// <returns>The encoded data</returns>
41+
public static Span<byte> EncodeDeployWithMaxDataLen(ulong maxDataLenght)
42+
{
43+
byte[] data = new byte[sizeof(uint) + sizeof(ulong)];
44+
data.WriteU32((uint)BPFLoaderProgramInstructions.Values.DeployWithMaxDataLen, MethodOffset);
45+
data.WriteU64(maxDataLenght, MethodOffset + sizeof(uint));
46+
return data;
47+
}
48+
/// <summary>
49+
/// Encode Upgrade data
50+
/// </summary>
51+
/// <returns>The encoded data</returns>
52+
public static Span<byte> EncodeUpgrade()
53+
{
54+
byte[] data = new byte[sizeof(uint)];
55+
data.WriteU32((uint)BPFLoaderProgramInstructions.Values.Upgrade, MethodOffset);
56+
return data;
57+
}
58+
/// <summary>
59+
/// Encode SetAuthority data
60+
/// </summary>
61+
/// <returns>The encoded data</returns>
62+
public static Span<byte> EncodeSetAuthority()
63+
{
64+
byte[] data = new byte[sizeof(uint)];
65+
data.WriteU32((uint)BPFLoaderProgramInstructions.Values.SetAuthority, MethodOffset);
66+
return data;
67+
}
68+
/// <summary>
69+
/// Encode Close data
70+
/// </summary>
71+
/// <returns>The encoded data</returns>
72+
public static Span<byte> EncodeClose()
73+
{
74+
byte[] data = new byte[sizeof(uint)];
75+
data.WriteU32((uint)BPFLoaderProgramInstructions.Values.Close, MethodOffset);
76+
return data;
77+
}
78+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections.Generic;
2+
3+
namespace Solnet.Programs;
4+
5+
internal static class BPFLoaderProgramInstructions
6+
{
7+
internal static readonly Dictionary<Values, string> Names = new()
8+
{
9+
{ Values.InitializeBuffer, "Initialize" },
10+
{ Values.Write, "Write" },
11+
{ Values.DeployWithMaxDataLen, "Deploy With Max Data Length" },
12+
{ Values.Upgrade, "Upgrade" },
13+
{ Values.SetAuthority, "SetAuthority" },
14+
{ Values.Close, "Close" },
15+
};
16+
/// <summary>
17+
/// Represents the instruction types />.
18+
/// </summary>
19+
internal enum Values : byte
20+
{
21+
InitializeBuffer = 0,
22+
Write = 1,
23+
DeployWithMaxDataLen = 2,
24+
Upgrade = 3,
25+
SetAuthority = 4,
26+
Close =5,
27+
28+
}
29+
30+
}

src/Solnet.Programs/Models/NameService/TokenData.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ public static TokenData Deserialize(byte[] input)
5353
var data = new ReadOnlySpan<byte>(input, 96, input.Length - 96);
5454
int offset = 0;
5555

56-
offset += data.GetBorshString(0, out var name);
57-
offset += data.GetBorshString(offset, out var ticker);
56+
offset += data.GetBorshStringSize(0, out var name);
57+
offset += data.GetBorshStringSize(offset, out var ticker);
5858

5959
var mint = data.GetPubKey(offset);
6060
offset += 32;
@@ -64,7 +64,7 @@ public static TokenData Deserialize(byte[] input)
6464
string website = null, logo = null;
6565

6666
if (data.GetBool(offset++))
67-
offset += data.GetBorshString(offset, out website);
67+
offset += data.GetBorshStringSize(offset, out website);
6868

6969
if (data.GetBool(offset++))
7070
data.GetBorshString(offset, out logo);

src/Solnet.Programs/Utilities/Deserialization.cs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,7 @@ public static float GetSingle(this ReadOnlySpan<byte> data, int offset)
201201

202202
return BinaryPrimitives.ReadSingleLittleEndian(data.Slice(offset, sizeof(float)));
203203
}
204-
205-
204+
206205
/// <summary>
207206
/// Get a boolean value from the span at the given offset.
208207
/// </summary>
@@ -234,6 +233,7 @@ public static (string EncodedString, int Length) DecodeBincodeString(this ReadOn
234233
return (EncodedString: Encoding.UTF8.GetString(stringBytes), Length: stringLength + sizeof(ulong));
235234
}
236235

236+
237237
/// <summary>
238238
/// Decodes a string from a transaction instruction.
239239
/// </summary>
@@ -242,19 +242,48 @@ public static (string EncodedString, int Length) DecodeBincodeString(this ReadOn
242242
/// <param name="result">The decoded data./>.</param>
243243
/// <returns>The length in bytes that was read from the original buffer, including the</returns>
244244
/// <exception cref="ArgumentOutOfRangeException">Thrown when the offset is too big for the span.</exception>
245-
public static int GetBorshString(this ReadOnlySpan<byte> data, int offset, out string result)
245+
public static string GetBorshString(this ReadOnlySpan<byte> data, int offset, out string result)
246246
{
247247
if (offset + sizeof(uint) > data.Length)
248248
throw new ArgumentOutOfRangeException(nameof(offset));
249-
250249
int stringLength = (int)data.GetU32(offset);
251250
byte[] stringBytes = data.GetSpan(offset + sizeof(uint), stringLength).ToArray();
252251
result = Encoding.UTF8.GetString(stringBytes);
253252

254-
return stringLength + sizeof(uint);
253+
return result;
255254
}
255+
/// <summary>
256+
/// Get size of the Borsh string
257+
/// </summary>
258+
/// <param name="data"></param>
259+
/// <param name="offset"></param>
260+
/// <param name="result"></param>
261+
/// <returns></returns>
262+
/// <exception cref="ArgumentOutOfRangeException"></exception>
263+
public static int GetBorshStringSize(this ReadOnlySpan<byte> data, int offset, out string result)
264+
{
265+
if (offset + sizeof(uint) > data.Length)
266+
throw new ArgumentOutOfRangeException(nameof(offset));
256267

268+
int stringLength = (int)data.GetU32(offset);
269+
byte[] stringBytes = data.GetSpan(offset + sizeof(uint), stringLength).ToArray();
270+
result = Encoding.UTF8.GetString(stringBytes);
271+
return stringLength + sizeof(uint);
272+
}
257273

274+
/// <summary>
275+
/// Get a Borsh ByteVector from an array at a given offset
276+
/// </summary>
277+
/// <param name="data">the array to read the data from</param>
278+
/// <param name="offset">the offset to begin reading the data</param>
279+
/// <returns></returns>
280+
public static Span<byte> GetBorshByteVector(this byte[] data, int offset)
281+
{
282+
if (offset + sizeof(uint) > data.Length)
283+
throw new ArgumentOutOfRangeException(nameof(offset));
284+
return data.AsSpan(offset + sizeof(uint), (int)new ReadOnlySpan<byte>(data).GetU32(offset));
285+
}
286+
258287
/// <summary>
259288
/// Get a span from the read-only span at the given offset with the given length.
260289
/// </summary>

0 commit comments

Comments
 (0)