11import { SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system' ;
2- import { Address , flattenTransactionPlan , getUtf8Encoder , InstructionPlan , TransactionMessage } from '@solana/kit' ;
2+ import {
3+ Address ,
4+ flattenTransactionPlan ,
5+ generateKeyPairSigner ,
6+ getUtf8Encoder ,
7+ InstructionPlan ,
8+ TransactionMessage ,
9+ } from '@solana/kit' ;
310import { expect , it } from 'vitest' ;
411
512import {
@@ -9,6 +16,7 @@ import {
916 Encoding ,
1017 findCanonicalPda ,
1118 Format ,
19+ getCreateMetadataInstructionPlanUsingExistingBuffer ,
1220 getCreateMetadataInstructionPlanUsingNewBuffer ,
1321 getExtendInstructionPlan ,
1422 parseProgramMetadataInstruction ,
@@ -163,6 +171,85 @@ it('packs the extend instructions densely when creating metadata by default', as
163171 expect ( getExtendLengths ( messages [ 0 ] , metadata ) ) . toEqual ( [ REALLOC_LIMIT , REALLOC_LIMIT , 25_000 - 2 * REALLOC_LIMIT ] ) ;
164172} ) ;
165173
174+ it ( 'accounts for the header when the data alone fits within the realloc limit' , async ( ) => {
175+ // Given a deployed program and an existing buffer holding exactly one realloc limit of data,
176+ // which together with the account header exceeds the limit.
177+ const client = await createTestClient ( ) ;
178+ const authority = await generateKeyPairSignerWithSol ( client ) ;
179+ const [ program , programData ] = await createDeployedProgram ( client , authority ) ;
180+ const [ metadata ] = await findCanonicalPda ( { program, seed : 'idl' } ) ;
181+ const buffer = await generateKeyPairSigner ( ) ;
182+
183+ // When we plan the metadata creation from that buffer with a single extend instruction per transaction.
184+ const plan = await getCreateMetadataInstructionPlanUsingExistingBuffer ( client , {
185+ authority,
186+ buffer : buffer . address ,
187+ dataLength : REALLOC_LIMIT ,
188+ metadata,
189+ payer : authority ,
190+ program,
191+ programData,
192+ seed : 'idl' ,
193+ encoding : Encoding . Utf8 ,
194+ compression : Compression . None ,
195+ dataSource : DataSource . Direct ,
196+ format : Format . Json ,
197+ singleExtendPerTransaction : true ,
198+ } ) ;
199+ const messages = await planMessages ( client , plan ) ;
200+
201+ // Then the allocation and a header-adjusted extend share the first transaction,
202+ // and the remaining bytes are extended before the write in the second one.
203+ expect ( getInstructionTypes ( messages [ 0 ] , metadata ) ) . toEqual ( [
204+ ProgramMetadataInstruction . Allocate ,
205+ ProgramMetadataInstruction . Extend ,
206+ ] ) ;
207+ expect ( getExtendLengths ( messages [ 0 ] , metadata ) ) . toEqual ( [ REALLOC_LIMIT - ACCOUNT_HEADER_LENGTH ] ) ;
208+ expect ( getInstructionTypes ( messages [ 1 ] , metadata ) ) . toEqual ( [
209+ ProgramMetadataInstruction . Extend ,
210+ ProgramMetadataInstruction . Write ,
211+ ProgramMetadataInstruction . Initialize ,
212+ ] ) ;
213+ expect ( getExtendLengths ( messages [ 1 ] , metadata ) ) . toEqual ( [ ACCOUNT_HEADER_LENGTH ] ) ;
214+ expect ( messages . every ( message => getGrowth ( message , metadata ) <= REALLOC_LIMIT ) ) . toBe ( true ) ;
215+ } ) ;
216+
217+ it ( 'adds a single extend instruction when the data alone fits within the realloc limit by default' , async ( ) => {
218+ // Given a deployed program and an existing buffer holding exactly one realloc limit of data.
219+ const client = await createTestClient ( ) ;
220+ const authority = await generateKeyPairSignerWithSol ( client ) ;
221+ const [ program , programData ] = await createDeployedProgram ( client , authority ) ;
222+ const [ metadata ] = await findCanonicalPda ( { program, seed : 'idl' } ) ;
223+ const buffer = await generateKeyPairSigner ( ) ;
224+
225+ // When we plan the metadata creation from that buffer without constraining the extend instructions.
226+ const plan = await getCreateMetadataInstructionPlanUsingExistingBuffer ( client , {
227+ authority,
228+ buffer : buffer . address ,
229+ dataLength : REALLOC_LIMIT ,
230+ metadata,
231+ payer : authority ,
232+ program,
233+ programData,
234+ seed : 'idl' ,
235+ encoding : Encoding . Utf8 ,
236+ compression : Compression . None ,
237+ dataSource : DataSource . Direct ,
238+ format : Format . Json ,
239+ } ) ;
240+ const messages = await planMessages ( client , plan ) ;
241+
242+ // Then everything fits in a single transaction with one full extend instruction.
243+ expect ( messages ) . toHaveLength ( 1 ) ;
244+ expect ( getInstructionTypes ( messages [ 0 ] , metadata ) ) . toEqual ( [
245+ ProgramMetadataInstruction . Allocate ,
246+ ProgramMetadataInstruction . Extend ,
247+ ProgramMetadataInstruction . Write ,
248+ ProgramMetadataInstruction . Initialize ,
249+ ] ) ;
250+ expect ( getExtendLengths ( messages [ 0 ] , metadata ) ) . toEqual ( [ REALLOC_LIMIT ] ) ;
251+ } ) ;
252+
166253async function planMessages ( client : TestClient , plan : InstructionPlan ) : Promise < TransactionMessage [ ] > {
167254 const transactionPlan = await client . planTransactions ( plan ) ;
168255 return flattenTransactionPlan ( transactionPlan ) . map ( single => single . message ) ;
0 commit comments