Skip to content

Commit 7ff577f

Browse files
committed
♻️ Making apply system API retro-compatible
1 parent 4bd83fe commit 7ff577f

File tree

21 files changed

+568
-356
lines changed

21 files changed

+568
-356
lines changed

Cargo.lock

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ which = "^7"
6262
tokio = { version = "^1", features = ["full"] }
6363
sysinfo = "=0.36.1"
6464
bytemuck_derive = "^1"
65+
const-crypto = "0.3.0"
6566

6667
[profile.release]
6768
overflow-checks = true

clients/csharp/Solana.Unity.Bolt/WorldProgram/Bolt/ApplySystem.cs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem(
5050
}
5151

5252
/// <summary>
53-
/// Apply a bundled system and/or bundled components by name, mirroring TS client behavior.
54-
/// - If systemId is a bundled System (program + name), we use "global:{name}_bolt_execute" discriminator.
55-
/// - For each component, if provided as bundled Component (program + name), we:
56-
/// * use the component name as the PDA seed and
57-
/// * build the component-specific update discriminator (name + _update or _update_with_session).
53+
/// Apply a bundled system and components, mirroring TS client behavior.
54+
/// Chooses among apply/applyWithSession/applyWithDiscriminator/applyWithSessionAndDiscriminator
55+
/// based on whether the System has a name (discriminator) and whether a session token is provided.
56+
/// Component discriminators are no longer sent; only component id + PDA pairs are included.
5857
/// </summary>
5958
public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem(
6059
PublicKey world,
@@ -69,7 +68,6 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem(
6968
programId ??= new(WorldProgram.ID);
7069

7170
var remainingAccounts = new global::System.Collections.Generic.List<Solana.Unity.Rpc.Models.AccountMeta>();
72-
var discriminators = new global::System.Collections.Generic.List<byte[]>();
7371

7472
foreach (var entry in entities)
7573
{
@@ -82,9 +80,6 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem(
8280
var pda = WorldProgram.FindComponentPda(comp.Program, entity, comp.Seeds(providedSeed));
8381
remainingAccounts.Add(Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(comp.Program, false));
8482
remainingAccounts.Add(Solana.Unity.Rpc.Models.AccountMeta.Writable(pda, false));
85-
86-
var compDiscriminator = comp.GetMethodDiscriminator(sessionToken != null ? "update_with_session" : "update");
87-
discriminators.Add(compDiscriminator);
8883
}
8984
}
9085

@@ -96,31 +91,48 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem(
9691
remainingAccounts.AddRange(extraAccounts);
9792
}
9893

99-
var systemDiscriminator = systemId.GetMethodDiscriminator("bolt_execute");
94+
bool hasSystemName = !string.IsNullOrEmpty(systemId.Name);
95+
var serializedArgs = SerializeArgs(args);
10096

10197
Solana.Unity.Rpc.Models.TransactionInstruction instruction;
10298
if (sessionToken != null)
10399
{
104-
var apply = new ApplyWithSessionAccounts()
100+
var accounts = new ApplyWithSessionAccounts()
105101
{
106102
BoltSystem = systemId.Program,
107103
Authority = authority,
108104
CpiAuth = WorldProgram.CpiAuthAddress,
109105
World = world,
110106
SessionToken = sessionToken,
111107
};
112-
instruction = WorldProgram.ApplyWithSession(apply, systemDiscriminator, discriminators.ToArray(), SerializeArgs(args), programId);
108+
if (hasSystemName)
109+
{
110+
var sysDisc = systemId.GetMethodDiscriminator("bolt_execute");
111+
instruction = WorldProgram.ApplyWithSessionAndDiscriminator(accounts, sysDisc, serializedArgs, programId);
112+
}
113+
else
114+
{
115+
instruction = WorldProgram.ApplyWithSession(accounts, serializedArgs, programId);
116+
}
113117
}
114118
else
115119
{
116-
var apply = new ApplyAccounts()
120+
var accounts = new ApplyAccounts()
117121
{
118122
BoltSystem = systemId.Program,
119123
Authority = authority,
120124
CpiAuth = WorldProgram.CpiAuthAddress,
121125
World = world,
122126
};
123-
instruction = WorldProgram.Apply(apply, systemDiscriminator, discriminators.ToArray(), SerializeArgs(args), programId);
127+
if (hasSystemName)
128+
{
129+
var sysDisc = systemId.GetMethodDiscriminator("bolt_execute");
130+
instruction = WorldProgram.ApplyWithDiscriminator(accounts, sysDisc, serializedArgs, programId);
131+
}
132+
else
133+
{
134+
instruction = WorldProgram.Apply(accounts, serializedArgs, programId);
135+
}
124136
}
125137

126138
// Append remaining accounts (component id+pda pairs and extras)

clients/csharp/Solana.Unity.Bolt/WorldProgram/World.cs

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Text;
77
using Solana.Unity.Programs;
8+
using Solana.Unity.Programs.Utilities;
89
using Solana.Unity.Wallet;
910
using Solana.Unity.Rpc.Models;
1011
using GplSession.Program;
@@ -16,6 +17,10 @@ namespace Program
1617
public partial class WorldProgram
1718
{
1819
public static readonly PublicKey CpiAuthAddress = new("B2f2y3QTBv346wE6nWKor72AUhUvFF6mPk7TWCF2QVhi");
20+
private static readonly byte[] IX_APPLY = new byte[] { 248, 243, 145, 24, 105, 50, 162, 225 };
21+
private static readonly byte[] IX_APPLY_WITH_DISCRIMINATOR = new byte[] { 126, 75, 184, 115, 193, 245, 69, 15 };
22+
private static readonly byte[] IX_APPLY_WITH_SESSION = new byte[] { 213, 69, 29, 230, 142, 107, 134, 103 };
23+
private static readonly byte[] IX_APPLY_WITH_SESSION_AND_DISCRIMINATOR = new byte[] { 156, 187, 1, 148, 179, 240, 139, 27 };
1924
public static Solana.Unity.Rpc.Models.TransactionInstruction AddEntity(AddEntityAccounts accounts, PublicKey programId = null)
2025
{
2126
programId ??= new(ID);
@@ -221,19 +226,6 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem(
221226
throw new ArgumentException("Component IDs and PDAs must be the same length");
222227
}
223228

224-
var discriminators = new List<byte[]>();
225-
foreach (var entity in systemInput)
226-
{
227-
if (sessionToken != null)
228-
{
229-
discriminators.Add(Bolt.World.GetDiscriminator("global:update_with_session"));
230-
}
231-
else
232-
{
233-
discriminators.Add(Bolt.World.GetDiscriminator("global:update"));
234-
}
235-
}
236-
237229
Solana.Unity.Rpc.Models.TransactionInstruction instruction;
238230
if (sessionToken != null) {
239231
var apply = new ApplyWithSessionAccounts() {
@@ -243,28 +235,125 @@ public static Solana.Unity.Rpc.Models.TransactionInstruction ApplySystem(
243235
World = world,
244236
SessionToken = sessionToken,
245237
};
246-
instruction = ApplyWithSession(apply, Bolt.World.GetDiscriminator("global:bolt_execute"), discriminators.ToArray(), args, programId);
238+
instruction = ApplyWithSession(apply, args, programId);
247239
} else {
248240
var apply = new ApplyAccounts() {
249241
BoltSystem = system,
250242
Authority = authority,
251243
CpiAuth = CpiAuthAddress,
252244
World = world,
253245
};
254-
instruction = Apply(apply, Bolt.World.GetDiscriminator("global:bolt_execute"), discriminators.ToArray(), args, programId);
246+
instruction = Apply(apply, args, programId);
255247
}
256248
for (int i = 0; i < componentIds.Count; i++) {
257249
instruction.Keys.Add(AccountMeta.ReadOnly(componentIds[i], false));
258250
instruction.Keys.Add(AccountMeta.Writable(componentPdas[i], false));
259251
}
260252

261-
if (componentIds.Count > 0) {
262-
// program id delimits the end of the component list
263-
instruction.Keys.Add(AccountMeta.ReadOnly(new PublicKey(WorldProgram.ID), false));
264-
}
265-
266253
return instruction;
267254
}
255+
256+
public static Solana.Unity.Rpc.Models.TransactionInstruction Apply(ApplyAccounts accounts, byte[] args, PublicKey programId = null)
257+
{
258+
programId ??= new(ID);
259+
List<Solana.Unity.Rpc.Models.AccountMeta> keys = new()
260+
{
261+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false),
262+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true),
263+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false),
264+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false)
265+
};
266+
byte[] _data = new byte[1200];
267+
int offset = 0;
268+
_data.WriteSpan(IX_APPLY, offset);
269+
offset += 8;
270+
_data.WriteS32(args.Length, offset);
271+
offset += 4;
272+
_data.WriteSpan(args, offset);
273+
offset += args.Length;
274+
byte[] resultData = new byte[offset];
275+
Array.Copy(_data, resultData, offset);
276+
return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData};
277+
}
278+
279+
public static Solana.Unity.Rpc.Models.TransactionInstruction ApplyWithDiscriminator(ApplyAccounts accounts, byte[] system_discriminator, byte[] args, PublicKey programId = null)
280+
{
281+
programId ??= new(ID);
282+
List<Solana.Unity.Rpc.Models.AccountMeta> keys = new()
283+
{
284+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false),
285+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true),
286+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false),
287+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false)
288+
};
289+
byte[] _data = new byte[1200];
290+
int offset = 0;
291+
_data.WriteSpan(IX_APPLY_WITH_DISCRIMINATOR, offset);
292+
offset += 8;
293+
_data.WriteS32(system_discriminator.Length, offset);
294+
offset += 4;
295+
_data.WriteSpan(system_discriminator, offset);
296+
offset += system_discriminator.Length;
297+
_data.WriteS32(args.Length, offset);
298+
offset += 4;
299+
_data.WriteSpan(args, offset);
300+
offset += args.Length;
301+
byte[] resultData = new byte[offset];
302+
Array.Copy(_data, resultData, offset);
303+
return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData};
304+
}
305+
306+
public static Solana.Unity.Rpc.Models.TransactionInstruction ApplyWithSession(ApplyWithSessionAccounts accounts, byte[] args, PublicKey programId = null)
307+
{
308+
programId ??= new(ID);
309+
List<Solana.Unity.Rpc.Models.AccountMeta> keys = new()
310+
{
311+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false),
312+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true),
313+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false),
314+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false),
315+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SessionToken, false)
316+
};
317+
byte[] _data = new byte[1200];
318+
int offset = 0;
319+
_data.WriteSpan(IX_APPLY_WITH_SESSION, offset);
320+
offset += 8;
321+
_data.WriteS32(args.Length, offset);
322+
offset += 4;
323+
_data.WriteSpan(args, offset);
324+
offset += args.Length;
325+
byte[] resultData = new byte[offset];
326+
Array.Copy(_data, resultData, offset);
327+
return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData};
328+
}
329+
330+
public static Solana.Unity.Rpc.Models.TransactionInstruction ApplyWithSessionAndDiscriminator(ApplyWithSessionAccounts accounts, byte[] system_discriminator, byte[] args, PublicKey programId = null)
331+
{
332+
programId ??= new(ID);
333+
List<Solana.Unity.Rpc.Models.AccountMeta> keys = new()
334+
{
335+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.BoltSystem, false),
336+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.Authority, true),
337+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.CpiAuth, false),
338+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.World, false),
339+
Solana.Unity.Rpc.Models.AccountMeta.ReadOnly(accounts.SessionToken, false)
340+
};
341+
byte[] _data = new byte[1200];
342+
int offset = 0;
343+
_data.WriteSpan(IX_APPLY_WITH_SESSION_AND_DISCRIMINATOR, offset);
344+
offset += 8;
345+
_data.WriteS32(system_discriminator.Length, offset);
346+
offset += 4;
347+
_data.WriteSpan(system_discriminator, offset);
348+
offset += system_discriminator.Length;
349+
_data.WriteS32(args.Length, offset);
350+
offset += 4;
351+
_data.WriteSpan(args, offset);
352+
offset += args.Length;
353+
byte[] resultData = new byte[offset];
354+
Array.Copy(_data, resultData, offset);
355+
return new Solana.Unity.Rpc.Models.TransactionInstruction{Keys = keys, ProgramId = programId.KeyBytes, Data = resultData};
356+
}
268357
}
269358
}
270359
}

0 commit comments

Comments
 (0)