@@ -10,25 +10,16 @@ import {
1010} from '@solana/spl-token' ;
1111import { PublicKey } from '@solana/web3.js' ;
1212import { LiteSVMProvider } from 'anchor-litesvm' ;
13- import { assert } from 'chai' ;
13+ import { assert , expect , use } from 'chai' ;
14+ import chaiAsPromised from 'chai-as-promised' ;
1415import { LiteSVM } from 'litesvm' ;
1516import IDL from '../target/idl/fundraiser.json' ;
1617import type { Fundraiser } from '../target/types/fundraiser' ;
1718
19+ use ( chaiAsPromised ) ;
20+
1821const PROGRAM_ID = new PublicKey ( IDL . address ) ;
1922
20- // Regression test for the missing mint binding on `checker.rs`.
21- //
22- // `contribute` and `refund` both carry `has_one = mint_to_raise` on the
23- // `fundraiser` account, so the mint supplied in the transaction must equal the
24- // one recorded at `initialize`. `check_contributions` omits that constraint, so
25- // its `mint_to_raise` (and the vault derived from it) is whatever the caller
26- // passes. A maker can therefore satisfy the goal check against a throwaway mint
27- // they control and trigger `close = maker`, destroying the real campaign that
28- // every contributor's refund depends on.
29- //
30- // With the constraint present, Anchor rejects the wrong mint before the handler
31- // runs and the campaign account survives.
3223describe ( 'fundraiser checker mint binding' , ( ) => {
3324 const client = new LiteSVM ( ) ;
3425 client . addProgramFromFile ( PROGRAM_ID , 'target/deploy/fundraiser.so' ) ;
@@ -49,13 +40,11 @@ describe('fundraiser checker mint binding', () => {
4940 let realMint : PublicKey ;
5041 let fakeMint : PublicKey ;
5142
52- it ( 'sets up a real campaign and a maker-controlled fake mint' , async ( ) => {
43+ it ( 'sets up a real campaign mint and a maker-controlled fake mint' , async ( ) => {
5344 client . airdrop ( maker . publicKey , BigInt ( anchor . web3 . LAMPORTS_PER_SOL ) ) ;
5445
55- // The real campaign mint, authority held by the provider wallet.
5646 const realMintKp = anchor . web3 . Keypair . generate ( ) ;
5747 realMint = realMintKp . publicKey ;
58- // The fake mint, authority held by the maker — the whole point of the attack.
5948 const fakeMintKp = anchor . web3 . Keypair . generate ( ) ;
6049 fakeMint = fakeMintKp . publicKey ;
6150
@@ -98,14 +87,11 @@ describe('fundraiser checker mint binding', () => {
9887 . signers ( [ maker ] )
9988 . rpc ( ) ;
10089
101- // The campaign state exists and remembers the real mint.
10290 const state = await program . account . fundraiser . fetch ( fundraiser ) ;
10391 assert . strictEqual ( state . mintToRaise . toBase58 ( ) , realMint . toBase58 ( ) ) ;
10492 } ) ;
10593
10694 it ( 'rejects check_contributions against a mint other than the one recorded' , async ( ) => {
107- // The maker funds the fundraiser's ATA *for the fake mint* to the goal.
108- // Anyone may create an ATA on the PDA's behalf.
10995 const fakeVault = getAssociatedTokenAddressSync ( fakeMint , fundraiser , true ) ;
11096 const makerFakeAta = getAssociatedTokenAddressSync ( fakeMint , maker . publicKey ) ;
11197
@@ -116,29 +102,21 @@ describe('fundraiser checker mint binding', () => {
116102 ) ;
117103 await provider . sendAndConfirm ( fundTx , [ maker ] ) ;
118104
119- // Call the payout instruction with the fake mint and its funded vault.
120- let rejected = false ;
121- try {
122- await program . methods
123- . checkContributions ( )
124- . accountsPartial ( {
125- maker : maker . publicKey ,
126- mintToRaise : fakeMint ,
127- fundraiser,
128- makerAta : makerFakeAta ,
129- vault : fakeVault ,
130- tokenProgram : TOKEN_PROGRAM_ID ,
131- } )
132- . signers ( [ maker ] )
133- . rpc ( ) ;
134- } catch ( _err ) {
135- rejected = true ;
136- }
137-
138- assert . isTrue ( rejected , 'check_contributions accepted a mint other than the one recorded at initialize' ) ;
139-
140- // The real campaign must still be alive — a wrong-mint call must not
141- // reach `close = maker`.
105+ const checkPromise = program . methods
106+ . checkContributions ( )
107+ . accountsPartial ( {
108+ maker : maker . publicKey ,
109+ mintToRaise : fakeMint ,
110+ fundraiser,
111+ makerAta : makerFakeAta ,
112+ vault : fakeVault ,
113+ tokenProgram : TOKEN_PROGRAM_ID ,
114+ } )
115+ . signers ( [ maker ] )
116+ . rpc ( ) ;
117+
118+ await expect ( checkPromise ) . to . eventually . be . rejectedWith ( anchor . AnchorError , 'ConstraintHasOne' ) ;
119+
142120 const state = await program . account . fundraiser . fetch ( fundraiser ) ;
143121 assert . strictEqual ( state . mintToRaise . toBase58 ( ) , realMint . toBase58 ( ) ) ;
144122 } ) ;
0 commit comments