• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bmresearch / Solnet / 15466182724

05 Jun 2025 11:45AM UTC coverage: 70.35% (-0.4%) from 70.792%
15466182724

push

github

web-flow
Merge pull request #498 from AltudePlatform/account-compression-program

Account compression program

1137 of 1834 branches covered (62.0%)

Branch coverage included in aggregate %.

148 of 239 new or added lines in 4 files covered. (61.92%)

5267 of 7269 relevant lines covered (72.46%)

1161944.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.67
/src/Solnet.Programs/AccountCompression/AccountCompressionProgramData.cs
1
using Solnet.Programs.Utilities;
2
using Solnet.Wallet;
3
using System;
4
using System.Collections.Generic;
5
using System.IO;
6
using System.Linq;
7
using System.Reflection;
8
using System.Text;
9
using System.Threading.Tasks;
10
using static Solnet.Programs.Models.Stake.State;
11

12
namespace Solnet.Programs.AccountCompression
13
{
14
    /// <summary>
15
    /// Represents the instruction data for the <see cref="AccountCompressionProgram"/>.
16
    /// </summary>
17
    internal static class AccountCompressionProgramData
18
    {
19
        /// <summary>
20
        /// The offset for the instruction discriminator in the instruction data.
21
        /// </summary>
22
        internal const int MethodOffset = 0;
23
        /// <summary>
24
        /// Encode the Append instruction data
25
        /// </summary>
26
        /// <param name="leaf">32-byte leaf data</param>
27
        /// <returns>Encoded byte array for the instruction data</returns>
28
        public static byte[] EncodeAppendData(byte[] leaf)
29
        {
30
            if (leaf == null || leaf.Length != 32)
1!
NEW
31
                throw new ArgumentException("Leaf must be 32 bytes");
×
32

33
            byte[] data = new byte[4 + 32]; // 4 bytes discriminator + 32 bytes leaf
1✔
34
            int offset = 0;
1✔
35

36
            // Write the 4-byte discriminator
37
            BitConverter.GetBytes((uint)AccountCompressionProgramInstructions.Values.Append).CopyTo(data, MethodOffset);
1✔
38
            offset += 4;
1✔
39

40
            // Write the 32-byte leaf data
41
            Buffer.BlockCopy(leaf, 0, data, offset, 32);
1✔
42

43
            return data;
1✔
44
        }
45
        /// <summary>
46
        /// Encodes the CloseEmptyTree instruction data.
47
        /// </summary>
48
        /// <returns></returns>
49
        internal static byte[] EncodeCloseEmptyTreeData()
50
        {
51
            // Only the discriminator (first 4 bytes) is needed
52
            var data = new byte[4];
1✔
53

54
            // You should replace this with the actual discriminator used by your Anchor program
55
            // For example: discriminator = Hash("global:close_empty_tree").Take(8) or a known constant
56
            uint discriminator = (uint)AccountCompressionProgramInstructions.Values.CloseEmptyTree;
1✔
57

58
            data.WriteU32(discriminator, 0);
1✔
59
            return data;
1✔
60
        }
61
        /// <summary>
62
        /// Encodes the InitEmptyMerkleTree instruction data.
63
        /// </summary>
64
        /// <param name="maxDepth"></param>
65
        /// <param name="maxBufferSize"></param>
66
        /// <returns></returns>
67
        internal static byte[] EncodeInitEmptyMerkleTreeData(byte maxDepth, byte maxBufferSize)
68
        {
69
            // Total size = 4 (discriminator) + 1 (maxDepth) + 1 (maxBufferSize) = 6 bytes
70
            byte[] data = new byte[6];
1✔
71
            int offset = 0;
1✔
72

73
            // 1. Instruction Discriminator (4 bytes)
74
            BitConverter.GetBytes((uint)AccountCompressionProgramInstructions.Values.InitEmptyMerkleTree)
1✔
75
                .CopyTo(data, MethodOffset);
1✔
76
            offset += 4;
1✔
77

78
            // 2. Max Depth (1 byte)
79
            data[offset++] = maxDepth;
1✔
80

81
            // 3. Max Buffer Size (1 byte)
82
            data[offset] = maxBufferSize;
1✔
83

84
            return data;
1✔
85
        }
86
        /// <summary>
87
        /// Encodes the ReplaceLeaf instruction data.
88
        /// </summary>
89
        /// <param name="newLeaf"></param>
90
        /// <param name="previousLeaf"></param>
91
        /// <param name="root"></param>
92
        /// <param name="index"></param>
93
        /// <returns></returns>
94
        /// <exception cref="ArgumentException"></exception>
95
        internal static byte[] EncodeReplaceLeafData(
96
            byte[] newLeaf,
97
            byte[] previousLeaf,
98
            byte[] root,
99
            uint index
100
        )
101
        {
102
            // Validate inputs
103
            if (newLeaf == null || newLeaf.Length != 32) throw new ArgumentException("newLeaf must be 32 bytes");
1!
104
            if (previousLeaf == null || previousLeaf.Length != 32) throw new ArgumentException("previousLeaf must be 32 bytes");
1!
105
            if (root == null || root.Length != 32) throw new ArgumentException("root must be 32 bytes");
1!
106

107
            // Total size: 4 (discriminator) + 32 (root) + 32 (previousLeaf) + 32 (newLeaf) + 4 (index) = 104 bytes
108
            byte[] data = new byte[104];
1✔
109
            int offset = 0;
1✔
110

111
            // 1. Instruction Discriminator (4 bytes)
112
            BitConverter.GetBytes((uint)AccountCompressionProgramInstructions.Values.ReplaceLeaf)
1✔
113
                .CopyTo(data, MethodOffset);
1✔
114
            offset += 4;
1✔
115

116
            // 2. Root [32 bytes]
117
            Buffer.BlockCopy(root, 0, data, offset, 32);
1✔
118
            offset += 32;
1✔
119

120
            // 3. Previous Leaf [32 bytes]
121
            Buffer.BlockCopy(previousLeaf, 0, data, offset, 32);
1✔
122
            offset += 32;
1✔
123

124
            // 4. New Leaf [32 bytes]
125
            Buffer.BlockCopy(newLeaf, 0, data, offset, 32);
1✔
126
            offset += 32;
1✔
127

128
            // 5. Index [4 bytes]
129
            BitConverter.GetBytes(index).CopyTo(data, offset);
1✔
130

131
            return data;
1✔
132
        }
133

134
        /// <summary>
135
        /// Encodes the InsertOrAppend instruction data.
136
        /// </summary>
137
        /// <param name="Leaf"></param>
138
        /// <param name="root"></param>
139
        /// <param name="index"></param>
140
        /// <returns></returns>
141
        /// <exception cref="ArgumentException"></exception>
142
        internal static byte[] EncodeInsertOrAppendData(
143
            byte[] Leaf,
144
            byte[] root,
145
            uint index
146
        )
147
        {
148
            // Validate inputs
149
            if (root == null || root.Length != 32) throw new ArgumentException("root must be 32 bytes");
1!
150

151
            // Total size: 4 (discriminator) + 32 (root) + 32 (Leaf) + 4 (index) = 104 bytes
152
            byte[] data = new byte[72];
1✔
153
            int offset = 0;
1✔
154

155
            // 1. Instruction Discriminator (4 bytes)
156
            BitConverter.GetBytes((uint)AccountCompressionProgramInstructions.Values.InsertOrAppend)
1✔
157
                .CopyTo(data, MethodOffset);
1✔
158
            offset += 4;
1✔
159

160
            // 2. Root [32 bytes]
161
            Buffer.BlockCopy(root, 0, data, offset, 32);
1✔
162
            offset += 32;
1✔
163

164
            // 3. Leaf [32 bytes]
165
            Buffer.BlockCopy(Leaf, 0, data, offset, 32);
1✔
166
            offset += 32;
1✔
167

168
            // 4. Index [4 bytes]
169
            BitConverter.GetBytes(index).CopyTo(data, offset);
1✔
170

171
            return data;
1✔
172
        }
173

174
        /// <summary>
175
        /// Encodes the TransferAuthority instruction data.
176
        /// </summary>
177
        /// <param name="newAuthority"></param>
178
        /// <returns></returns>
179
        /// <exception cref="ArgumentNullException"></exception>
180
        internal static byte[] EncodeTransferAuthorityData(PublicKey newAuthority)
181
        {
182
            if (newAuthority is null) throw new ArgumentNullException(nameof(newAuthority));
1!
183

184
            byte[] data = new byte[36];
1✔
185

186
            // 1. Add instruction discriminator
187
            data.WriteU32((uint)AccountCompressionProgramInstructions.Values.TransferAuthority,MethodOffset);
1✔
188

189
            // 2. Add new authority public key (32 bytes)
190
            data.WritePubKey(newAuthority, 4);
1✔
191

192
            return data;
1✔
193
        }
194
        /// <summary>
195
        /// Encodes the VerifyLeaf instruction data.
196
        /// </summary>
197
        /// <param name="root"></param>
198
        /// <param name="leaf"></param>
199
        /// <param name="index"></param>
200
        /// <returns></returns>
201
        /// <exception cref="ArgumentException"></exception>
202
        public static byte[] EncodeVerifyLeafData(byte[] root, byte[] leaf, uint index)
203
        {
204
            if (root == null || root.Length != 32) throw new ArgumentException("Root must be 32 bytes", nameof(root));
1!
205
            if (leaf == null || leaf.Length != 32) throw new ArgumentException("Leaf must be 32 bytes", nameof(leaf));
1!
206

207
            // Total size: 4 (discriminator) + 32 (root) + 32 (leaf) + 4 (index) = 72 bytes
208
            byte[] data = new byte[72];
1✔
209
            int offset = 0;
1✔
210

211
            // 1. Instruction Discriminator (4 bytes)
212
            BitConverter.GetBytes((uint)AccountCompressionProgramInstructions.Values.VerifyLeaf).CopyTo(data, MethodOffset);
1✔
213
            offset += 4;
1✔
214

215
            // 2. Root (32 bytes)
216
            Buffer.BlockCopy(root, 0, data, offset, 32);
1✔
217
            offset += 32;
1✔
218

219
            // 3. Leaf (32 bytes)
220
            Buffer.BlockCopy(leaf, 0, data, offset, 32);
1✔
221
            offset += 32;
1✔
222

223
            // 4. Index (4 bytes)
224
            BitConverter.GetBytes(index).CopyTo(data, offset);
1✔
225

226
            return data;
1✔
227
        }
228
        /// <summary>
229
        /// Decodes the AppendLeaf instruction data.
230
        /// </summary>
231
        /// <param name="decodedInstruction"></param>
232
        /// <param name="data"></param>
233
        /// <param name="keys"></param>
234
        /// <param name="keyIndices"></param>
235
        internal static void DecodeAppendLeafData(
236
            DecodedInstruction decodedInstruction,
237
            ReadOnlySpan<byte> data,
238
            IList<PublicKey> keys,
239
            byte[] keyIndices)
240
        {
241
            // Decode accounts
NEW
242
            decodedInstruction.Values.Add("authority", keys[keyIndices[0]]);
×
NEW
243
            decodedInstruction.Values.Add("merkle_tree", keys[keyIndices[1]]);
×
244

245
            // Instruction data offsets:
246
            // 0..4   = discriminator
247
            // 4..36  = leaf (32 bytes)
NEW
248
            var leafBytes = data.GetBytes(4, 32);
×
NEW
249
            decodedInstruction.Values.Add("leaf", leafBytes);
×
NEW
250
        }
×
251
        /// <summary>
252
        /// Decodes the CloseEmptyTree instruction data.
253
        /// </summary>
254
        /// <param name="decodedInstruction"></param>
255
        /// <param name="data"></param>
256
        /// <param name="keys"></param>
257
        /// <param name="keyIndices"></param>
258
        /// <exception cref="ArgumentException"></exception>
259
        internal static void DecodeCloseEmptyTreeData(
260
            DecodedInstruction decodedInstruction,
261
            ReadOnlySpan<byte> data,
262
            IList<PublicKey> keys,
263
            byte[] keyIndices)
264
        {
NEW
265
            if (data.Length < 4) throw new ArgumentException("Instruction data too short.");
×
266

NEW
267
            uint discriminator = BitConverter.ToUInt32(data.Slice(0, 4));
×
268

NEW
269
            decodedInstruction.Values ??= new Dictionary<string, object>();
×
NEW
270
            decodedInstruction.Values.Add("instruction", discriminator);
×
271

272
            // Match accounts: [merkle_tree, authority, recipient]
NEW
273
            if (keyIndices.Length < 3) throw new ArgumentException("Insufficient account keys for CloseEmptyTree");
×
274

NEW
275
            decodedInstruction.Values.Add("merkle_tree", keys[keyIndices[0]]);
×
NEW
276
            decodedInstruction.Values.Add("authority", keys[keyIndices[1]]);
×
NEW
277
            decodedInstruction.Values.Add("recipient", keys[keyIndices[2]]);
×
NEW
278
        }
×
279
        /// <summary>
280
        /// Decodes the InitEmptyMerkleTree instruction data.
281
        /// </summary>
282
        /// <param name="decodedInstruction"></param>
283
        /// <param name="data"></param>
284
        /// <param name="keys"></param>
285
        /// <param name="keyIndices"></param>
286
        internal static void DecodeInitEmptyMerkleTreeData(
287
            DecodedInstruction decodedInstruction,
288
            ReadOnlySpan<byte> data,
289
            IList<PublicKey> keys,
290
            byte[] keyIndices)
291
        {
292
            // Accounts used
NEW
293
            decodedInstruction.Values.Add("merkle_tree", keys[keyIndices[0]]);
×
NEW
294
            decodedInstruction.Values.Add("authority", keys[keyIndices[1]]);
×
295

296
            // Data: [4 bytes discriminator][1 byte maxDepth][1 byte maxBufferSize]
NEW
297
            decodedInstruction.Values.Add("max_depth", data[4]);
×
NEW
298
            decodedInstruction.Values.Add("max_buffer_size", data[5]);
×
NEW
299
        }
×
300
        /// <summary>
301
        /// Decodes the ReplaceLeaf instruction data.
302
        /// </summary>
303
        /// <param name="decodedInstruction"></param>
304
        /// <param name="data"></param>
305
        /// <param name="keys"></param>
306
        /// <param name="keyIndices"></param>
307
        internal static void DecodeReplaceLeafData(
308
            DecodedInstruction decodedInstruction,
309
            ReadOnlySpan<byte> data,
310
            IList<PublicKey> keys,
311
            byte[] keyIndices)
312
        {
313
            // Decode accounts
314

NEW
315
            decodedInstruction.Values.Add("merkle_tree", keys[keyIndices[0]]);
×
NEW
316
            decodedInstruction.Values.Add("authority", keys[keyIndices[1]]);
×
317

318
            // 0..4    = discriminator
319
            // 4..36   = root
320
            // 36..68  = previousLeaf
321
            // 68..100 = newLeaf
322
            // 100..104 = index (uint32)
323

NEW
324
            decodedInstruction.Values.Add("root", data.GetBytes(4, 32));
×
NEW
325
            decodedInstruction.Values.Add("previous_leaf", data.GetBytes(36, 32));
×
NEW
326
            decodedInstruction.Values.Add("new_leaf", data.GetBytes(68, 32));
×
NEW
327
            decodedInstruction.Values.Add("index", data.GetU32(100));
×
NEW
328
        }
×
329
        /// <summary>
330
        /// Decodes the InsertOrAppend instruction data.
331
        /// </summary>
332
        /// <param name="decodedInstruction"></param>
333
        /// <param name="data"></param>
334
        /// <param name="keys"></param>
335
        /// <param name="keyIndices"></param>
336
        internal static void DecodeInsertOrAppendData(
337
            DecodedInstruction decodedInstruction,
338
            ReadOnlySpan<byte> data,
339
            IList<PublicKey> keys,
340
            byte[] keyIndices)
341
        {
342
            // Decode accounts
343

NEW
344
            decodedInstruction.Values.Add("merkle_tree", keys[keyIndices[0]]);
×
NEW
345
            decodedInstruction.Values.Add("authority", keys[keyIndices[1]]);
×
346

347
            // 0..4    = discriminator
348
            // 4..36   = root
349
            // 36..68  = previousLeaf
350
            // 68..100 = newLeaf
351
            // 100..104 = index (uint32)
352

NEW
353
            decodedInstruction.Values.Add("root", data.GetBytes(4,32));
×
NEW
354
            decodedInstruction.Values.Add("leaf", data.GetBytes(36,32));
×
NEW
355
            decodedInstruction.Values.Add("index", data.GetU32(68));
×
NEW
356
        }
×
357
        /// <summary>
358
        /// Decodes the TransferAuthority instruction data.
359
        /// </summary>
360
        /// <param name="decodedInstruction"></param>
361
        /// <param name="data"></param>
362
        /// <param name="keys"></param>
363
        /// <param name="keyIndices"></param>
364
        internal static void DecodeTransferAuthorityInstruction(
365
            DecodedInstruction decodedInstruction,
366
            ReadOnlySpan<byte> data,
367
            IList<PublicKey> keys,
368
            byte[] keyIndices)
369
        {
370
            // Decode accounts
371

NEW
372
            decodedInstruction.Values.Add("merkle_tree", keys[keyIndices[0]]);
×
NEW
373
            decodedInstruction.Values.Add("authority", keys[keyIndices[1]]);
×
NEW
374
            decodedInstruction.Values.Add("new_authority", keys[keyIndices[2]]);
×
375

376
            // Instruction data offsets:
377
            // 0..4   = discriminator
378
            // 4..36  = newAuthority public key
NEW
379
            var newAuthority = data.GetPubKey(4);
×
NEW
380
            decodedInstruction.Values.Add("new_authority", newAuthority);
×
NEW
381
        }
×
382
        /// <summary>
383
        /// Decodes the VerifyLeaf instruction data.
384
        /// </summary>
385
        /// <param name="decodedInstruction"></param>
386
        /// <param name="data"></param>
387
        /// <param name="keys"></param>
388
        /// <param name="keyIndices"></param>
389
        /// <exception cref="ArgumentException"></exception>
390
        internal static void DecodeVerifyLeafData(
391
            DecodedInstruction decodedInstruction,
392
            ReadOnlySpan<byte> data,
393
            IList<PublicKey> keys,
394
            byte[] keyIndices)
395
        {
396
            // This instruction typically has only one key: merkleTree
NEW
397
            decodedInstruction.Values.Add("merkle_tree", keys[keyIndices[0]]);
×
398

399
            // Decode data
NEW
400
            if (data.Length < 72) throw new ArgumentException("Invalid data length for VerifyLeaf");
×
401

NEW
402
            var root = data.GetBytes(4,32);
×
NEW
403
            var leaf = data.GetBytes(36,32);
×
NEW
404
            var index = data.GetU32(68);
×
405

NEW
406
            decodedInstruction.Values.Add("root", root);
×
NEW
407
            decodedInstruction.Values.Add("leaf", leaf);
×
NEW
408
            decodedInstruction.Values.Add("index", index);
×
NEW
409
        }
×
410
    }
411

412

413
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc