@@ -176,7 +176,6 @@ describe('Token Fundraiser (Pinocchio)', () => {
176176 contributorAccount : Address ,
177177 contributorAta : Address ,
178178 vault : Address ,
179- bump : number ,
180179 amount : bigint ,
181180 ) {
182181 return {
@@ -191,7 +190,8 @@ describe('Token Fundraiser (Pinocchio)', () => {
191190 { address : TOKEN_PROGRAM_ADDRESS , role : AccountRole . READONLY } ,
192191 { address : SYSTEM_PROGRAM_ADDRESS , role : AccountRole . READONLY } ,
193192 ] ,
194- data : concatBytes ( Uint8Array . of ( 1 ) , u64 ( amount ) , Uint8Array . of ( bump ) ) ,
193+ // The contributor PDA bump is derived on-chain, not supplied here.
194+ data : concatBytes ( Uint8Array . of ( 1 ) , u64 ( amount ) ) ,
195195 } ;
196196 }
197197
@@ -261,7 +261,7 @@ describe('Token Fundraiser (Pinocchio)', () => {
261261 const contributor = await generateKeyPairSigner ( ) ;
262262 svm . airdrop ( contributor . address , lamports ( 1_000_000_000n ) ) ;
263263 const contributorAta = await fundAta ( maker , maker , contributor . address , mint . address , 10n ) ;
264- const [ contributorAccount , cbump ] = await contributorPda ( fundraiser , contributor . address ) ;
264+ const [ contributorAccount ] = await contributorPda ( fundraiser , contributor . address ) ;
265265
266266 send (
267267 await tx ( contributor , [
@@ -272,7 +272,6 @@ describe('Token Fundraiser (Pinocchio)', () => {
272272 contributorAccount ,
273273 contributorAta ,
274274 vault ,
275- cbump ,
276275 10n ,
277276 ) ,
278277 ] ) ,
@@ -323,7 +322,7 @@ describe('Token Fundraiser (Pinocchio)', () => {
323322 const contributor = await generateKeyPairSigner ( ) ;
324323 svm . airdrop ( contributor . address , lamports ( 1_000_000_000n ) ) ;
325324 const contributorAta = await fundAta ( maker , maker , contributor . address , mint . address , 10n ) ;
326- const [ contributorAccount , cbump ] = await contributorPda ( fundraiser , contributor . address ) ;
325+ const [ contributorAccount ] = await contributorPda ( fundraiser , contributor . address ) ;
327326 send (
328327 await tx ( contributor , [
329328 contributeIx (
@@ -333,7 +332,6 @@ describe('Token Fundraiser (Pinocchio)', () => {
333332 contributorAccount ,
334333 contributorAta ,
335334 vault ,
336- cbump ,
337335 10n ,
338336 ) ,
339337 ] ) ,
@@ -373,10 +371,10 @@ describe('Token Fundraiser (Pinocchio)', () => {
373371 const victim = await generateKeyPairSigner ( ) ;
374372 svm . airdrop ( victim . address , lamports ( 1_000_000_000n ) ) ;
375373 const victimAta = await fundAta ( maker , maker , victim . address , mint . address , 1n ) ;
376- const [ victimAccount , victimBump ] = await contributorPda ( fundraiser , victim . address ) ;
374+ const [ victimAccount ] = await contributorPda ( fundraiser , victim . address ) ;
377375 send (
378376 await tx ( victim , [
379- contributeIx ( victim , mint . address , fundraiser , victimAccount , victimAta , vault , victimBump , 1n ) ,
377+ contributeIx ( victim , mint . address , fundraiser , victimAccount , victimAta , vault , 1n ) ,
380378 ] ) ,
381379 'victim contribute' ,
382380 ) ;
@@ -387,7 +385,7 @@ describe('Token Fundraiser (Pinocchio)', () => {
387385 const attackerAta = await fundAta ( maker , maker , attacker . address , mint . address , 1n ) ;
388386 sendExpectingFailure (
389387 await tx ( attacker , [
390- contributeIx ( attacker , mint . address , fundraiser , victimAccount , attackerAta , vault , victimBump , 1n ) ,
388+ contributeIx ( attacker , mint . address , fundraiser , victimAccount , attackerAta , vault , 1n ) ,
391389 ] ) ,
392390 'contribution into a substituted record' ,
393391 ) ;
@@ -396,6 +394,40 @@ describe('Token Fundraiser (Pinocchio)', () => {
396394 assert . equal ( tokenAmount ( vault ) , 1n , 'vault only holds the recorded contribution' ) ;
397395 } ) ;
398396
397+ it ( 'Rejects a contributor record that is not the canonical PDA' , async ( ) => {
398+ const maker = await generateKeyPairSigner ( ) ;
399+ svm . airdrop ( maker . address , lamports ( 1_000_000_000n ) ) ;
400+ const mint = await createMint ( maker , 0 , maker . address ) ;
401+
402+ const [ fundraiser , bump ] = await fundraiserPda ( maker . address ) ;
403+ const [ vault ] = await findAssociatedTokenPda ( {
404+ owner : fundraiser ,
405+ mint : mint . address ,
406+ tokenProgram : TOKEN_PROGRAM_ADDRESS ,
407+ } ) ;
408+ send ( await tx ( maker , [ initializeIx ( maker , mint . address , fundraiser , vault , bump , 100n , 30 ) ] ) , 'initialize' ) ;
409+
410+ // The record address is derived on-chain, so a contributor cannot open a
411+ // second record for themselves at any other address — including one
412+ // derived from a non-canonical bump. A record that is not the canonical
413+ // PDA is refused before it can be created, which also keeps every record
414+ // reachable by `refund` (which only ever derives the canonical address).
415+ const contributor = await generateKeyPairSigner ( ) ;
416+ svm . airdrop ( contributor . address , lamports ( 1_000_000_000n ) ) ;
417+ const contributorAta = await fundAta ( maker , maker , contributor . address , mint . address , 5n ) ;
418+ const notTheCanonicalPda = ( await generateKeyPairSigner ( ) ) . address ;
419+
420+ sendExpectingFailure (
421+ await tx ( contributor , [
422+ contributeIx ( contributor , mint . address , fundraiser , notTheCanonicalPda , contributorAta , vault , 5n ) ,
423+ ] ) ,
424+ 'contribution into a non-canonical record' ,
425+ ) ;
426+
427+ assert . equal ( tokenAmount ( contributorAta ) , 5n , 'contributor kept their tokens' ) ;
428+ assert . isNotOk ( svm . getAccount ( notTheCanonicalPda ) ?. exists , 'no second record was created' ) ;
429+ } ) ;
430+
399431 it ( 'Ignores unrecorded direct transfers into the vault' , async ( ) => {
400432 const maker = await generateKeyPairSigner ( ) ;
401433 svm . airdrop ( maker . address , lamports ( 1_000_000_000n ) ) ;
@@ -414,7 +446,7 @@ describe('Token Fundraiser (Pinocchio)', () => {
414446 const contributor = await generateKeyPairSigner ( ) ;
415447 svm . airdrop ( contributor . address , lamports ( 1_000_000_000n ) ) ;
416448 const contributorAta = await fundAta ( maker , maker , contributor . address , mint . address , 10n ) ;
417- const [ contributorAccount , cbump ] = await contributorPda ( fundraiser , contributor . address ) ;
449+ const [ contributorAccount ] = await contributorPda ( fundraiser , contributor . address ) ;
418450 send (
419451 await tx ( contributor , [
420452 contributeIx (
@@ -424,7 +456,6 @@ describe('Token Fundraiser (Pinocchio)', () => {
424456 contributorAccount ,
425457 contributorAta ,
426458 vault ,
427- cbump ,
428459 10n ,
429460 ) ,
430461 ] ) ,
0 commit comments