@@ -19,16 +19,22 @@ import {
1919 MintProofV2 ,
2020 Mode ,
2121 TENSOR_WHITELIST_ERROR__FAILED_MERKLE_PROOF_VERIFICATION ,
22+ TENSOR_WHITELIST_ERROR__INVALID_AUTHORITY ,
2223 TENSOR_WHITELIST_ERROR__NOT_MERKLE_ROOT ,
24+ fetchMaybeMintProofV2 ,
2325 fetchMintProofV2 ,
2426 findMintProofV2Pda ,
27+ getCloseMintProofV2Instruction ,
2528 getInitUpdateMintProofV2InstructionAsync ,
2629 intoAddress ,
2730} from '../src' ;
2831import {
2932 MAX_PROOF_LENGTH ,
33+ SLOT_DELAY ,
34+ TRANSACTION_FEE ,
3035 createMintProofThrows ,
3136 createWhitelist ,
37+ expectCustomError ,
3238 updateWhitelist ,
3339 upsertMintProof ,
3440} from './_common' ;
@@ -546,3 +552,190 @@ test('invalid whitelist fails', async (t) => {
546552 code : ANCHOR_ERROR__ACCOUNT_NOT_INITIALIZED ,
547553 } ) ;
548554} ) ;
555+
556+ test ( 'cannot close the mint proof account before slot delay if not original signer' , async ( t ) => {
557+ const client = createDefaultSolanaClient ( ) ;
558+
559+ const updateAuthority = await generateKeyPairSignerWithSol ( client ) ;
560+ const mintProofSigner = await generateKeyPairSignerWithSol ( client ) ;
561+ const notMintProofSigner = await generateKeyPairSignerWithSol ( client ) ;
562+
563+ const { mint } = await createDefaultNft ( {
564+ client,
565+ payer : mintProofSigner ,
566+ authority : mintProofSigner ,
567+ owner : mintProofSigner . address ,
568+ } ) ;
569+
570+ const {
571+ root,
572+ proofs : [ p ] ,
573+ } = await generateTreeOfSize ( 10 , [ mint ] ) ;
574+
575+ const conditions : Condition [ ] = [
576+ { mode : Mode . MerkleTree , value : intoAddress ( root ) } ,
577+ ] ;
578+
579+ const { whitelist } = await createWhitelist ( {
580+ client,
581+ updateAuthority,
582+ conditions,
583+ } ) ;
584+
585+ const [ mintProof ] = await findMintProofV2Pda ( { mint, whitelist } ) ;
586+
587+ const createMintProofIx = await getInitUpdateMintProofV2InstructionAsync ( {
588+ payer : mintProofSigner ,
589+ mint,
590+ mintProof,
591+ whitelist,
592+ proof : p . proof ,
593+ } ) ;
594+
595+ await pipe (
596+ await createDefaultTransaction ( client , mintProofSigner ) ,
597+ ( tx ) => appendTransactionMessageInstruction ( createMintProofIx , tx ) ,
598+ ( tx ) => signAndSendTransaction ( client , tx )
599+ ) ;
600+
601+ const creationSlot = ( await fetchMintProofV2 ( client . rpc , mintProof ) ) . data
602+ . creationSlot ;
603+
604+ // If not original signer => Can't close it with own address as payer...
605+ const closeMintProofIxWrongAddress = getCloseMintProofV2Instruction ( {
606+ payer : mintProofSigner . address ,
607+ mintProof,
608+ signer : notMintProofSigner ,
609+ } ) ;
610+
611+ const promise = pipe (
612+ await createDefaultTransaction ( client , notMintProofSigner ) ,
613+ ( tx ) =>
614+ appendTransactionMessageInstruction ( closeMintProofIxWrongAddress , tx ) ,
615+ ( tx ) => signAndSendTransaction ( client , tx )
616+ ) ;
617+
618+ await expectCustomError (
619+ t ,
620+ promise ,
621+ TENSOR_WHITELIST_ERROR__INVALID_AUTHORITY
622+ ) ;
623+
624+ // Mint proof is still open and unchanged
625+ let mintProofAcc = await fetchMintProofV2 ( client . rpc , mintProof ) ;
626+ t . like ( mintProofAcc . data , {
627+ proof : p . proof ,
628+ creationSlot,
629+ proofLen : p . proof . length ,
630+ payer : mintProofSigner . address ,
631+ } ) ;
632+
633+ // ... and also not with the correct address as payer (DOS attack vector)
634+ const closeMintProofIxNotAllowed = getCloseMintProofV2Instruction ( {
635+ payer : mintProofSigner . address ,
636+ mintProof,
637+ signer : notMintProofSigner ,
638+ } ) ;
639+
640+ const promiseV2 = pipe (
641+ await createDefaultTransaction ( client , mintProofSigner ) ,
642+ ( tx ) => appendTransactionMessageInstruction ( closeMintProofIxNotAllowed , tx ) ,
643+ ( tx ) => signAndSendTransaction ( client , tx )
644+ ) ;
645+
646+ await expectCustomError (
647+ t ,
648+ promiseV2 ,
649+ TENSOR_WHITELIST_ERROR__INVALID_AUTHORITY
650+ ) ;
651+
652+ // Mint proof is still open and unchanged
653+ mintProofAcc = await fetchMintProofV2 ( client . rpc , mintProof ) ;
654+ t . like ( mintProofAcc . data , {
655+ proof : p . proof ,
656+ creationSlot,
657+ proofLen : p . proof . length ,
658+ payer : mintProofSigner . address ,
659+ } ) ;
660+
661+ // Assert that the test ran WITHIN expected slot delay
662+ const currentSlot = await client . rpc . getSlot ( ) . send ( ) ;
663+ t . assert ( currentSlot - creationSlot < SLOT_DELAY ) ;
664+ } ) ;
665+
666+ test ( 'mint proof can be closed by original signer before slot delay - receives rent back' , async ( t ) => {
667+ const client = createDefaultSolanaClient ( ) ;
668+
669+ const mintProofSigner = await generateKeyPairSignerWithSol ( client ) ;
670+ const updateAuthority = await generateKeyPairSignerWithSol ( client ) ;
671+ const { mint } = await createDefaultNft ( {
672+ client,
673+ payer : mintProofSigner ,
674+ authority : mintProofSigner ,
675+ owner : mintProofSigner . address ,
676+ } ) ;
677+
678+ const {
679+ root,
680+ proofs : [ p ] ,
681+ } = await generateTreeOfSize ( 10 , [ mint ] ) ;
682+
683+ const conditions : Condition [ ] = [
684+ { mode : Mode . MerkleTree , value : intoAddress ( root ) } ,
685+ ] ;
686+
687+ const { whitelist } = await createWhitelist ( {
688+ client,
689+ updateAuthority,
690+ conditions,
691+ } ) ;
692+
693+ const [ mintProof ] = await findMintProofV2Pda ( { mint, whitelist } ) ;
694+
695+ const beforeFunds = (
696+ await client . rpc . getBalance ( mintProofSigner . address ) . send ( )
697+ ) . value ;
698+
699+ const createMintProofIx = await getInitUpdateMintProofV2InstructionAsync ( {
700+ payer : mintProofSigner ,
701+ mint,
702+ mintProof,
703+ whitelist,
704+ proof : p . proof ,
705+ } ) ;
706+
707+ await pipe (
708+ await createDefaultTransaction ( client , mintProofSigner ) ,
709+ ( tx ) => appendTransactionMessageInstruction ( createMintProofIx , tx ) ,
710+ ( tx ) => signAndSendTransaction ( client , tx )
711+ ) ;
712+ const creationSlot = ( await fetchMintProofV2 ( client . rpc , mintProof ) ) . data
713+ . creationSlot ;
714+
715+ const closeMintProofIxOriginalSigner = getCloseMintProofV2Instruction ( {
716+ payer : mintProofSigner . address ,
717+ mintProof,
718+ signer : mintProofSigner ,
719+ } ) ;
720+
721+ await pipe (
722+ await createDefaultTransaction ( client , mintProofSigner ) ,
723+ ( tx ) =>
724+ appendTransactionMessageInstruction ( closeMintProofIxOriginalSigner , tx ) ,
725+ ( tx ) => signAndSendTransaction ( client , tx )
726+ ) ;
727+
728+ const afterFunds = (
729+ await client . rpc . getBalance ( mintProofSigner . address ) . send ( )
730+ ) . value ;
731+
732+ // Mint proof is closed now...
733+ t . assert (
734+ ( await fetchMaybeMintProofV2 ( client . rpc , mintProof ) ) . exists === false
735+ ) ;
736+ // ... the original signer received rent back ...
737+ t . assert ( afterFunds === beforeFunds - TRANSACTION_FEE * 2n ) ; // Creation + Closing tx fees subtracted
738+ // ... within the slot delay
739+ const currentSlot = await client . rpc . getSlot ( ) . send ( ) ;
740+ t . assert ( currentSlot - creationSlot < SLOT_DELAY ) ;
741+ } ) ;
0 commit comments