According to the contest's docs the creator of a crowdfunding campaign should be able to withdraw the funds after a successful campaign. Judging if a campaign was successful or not implies two things:
According to the docs Contributors "Can request refunds under if the campaign fails to meet the goal and the deadline is reached".
This means that the funds should remain in the contract until the deadline is reached.
async function airdropSol(publicKey: PublicKey, amount: number) {
const airdropTx = await anchor
.getProvider()
.connection.requestAirdrop(publicKey, amount);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx: string) {
const latestBlockHash = await anchor
.getProvider()
.connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
it.only("allows creator to withdraw funds even if the goal is not met", async () => {
const testFundName = "Bug Test Fund";
const testDescription = "Test fund for withdrawal bug demonstration";
const testGoal = new anchor.BN(10_000_000_000);
const depositAmount = new anchor.BN(4_000_000_000);
const [testFundPDA] = await PublicKey.findProgramAddress(
[Buffer.from(testFundName), creator.publicKey.toBuffer()],
program.programId
);
await program.methods
.fundCreate(testFundName, testDescription, testGoal)
.accounts({
fund: testFundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
const [creatorContributionPDA] = await PublicKey.findProgramAddress(
[testFundPDA.toBuffer(), creator.publicKey.toBuffer()],
program.programId
);
await program.methods
.contribute(depositAmount)
.accounts({
fund: testFundPDA,
contributor: creator.publicKey,
contribution: creatorContributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
await airdropSol(otherUser.publicKey, 5_000_000_000);
const [otherContributionPDA] = await PublicKey.findProgramAddress(
[testFundPDA.toBuffer(), otherUser.publicKey.toBuffer()],
program.programId
);
await program.methods
.contribute(depositAmount)
.accounts({
fund: testFundPDA,
contributor: otherUser.publicKey,
contribution: otherContributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([otherUser])
.rpc();
const fundAccount = await program.account.fund.fetch(testFundPDA);
console.log("Fund goal raise :", fundAccount.goal.toString());
console.log("Fund amount raised:", fundAccount.amountRaised.toString());
expect(fundAccount.amountRaised.eq(new anchor.BN(8_000_000_000))).to.be
.true;
expect(fundAccount.amountRaised.lt(testGoal)).to.be.true;
const fundBalance = await provider.connection.getBalance(testFundPDA);
console.log("Fund account lamports:", fundBalance);
const creatorBalanceBefore = await provider.connection.getBalance(
creator.publicKey
);
await program.methods
.withdraw()
.accounts({
fund: testFundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
const creatorBalanceAfter = await provider.connection.getBalance(
creator.publicKey
);
console.log("Creator balance before withdrawal:", creatorBalanceBefore);
console.log("Creator balance after withdrawal:", creatorBalanceAfter);
expect(creatorBalanceAfter).to.be.gt(creatorBalanceBefore);
});
You can clearly see from the test that the goal of the crowdfunding campaign was 10 SOL, users deposited 8 SOL, and the creator withdrew the funds.
pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
+ if ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
+ return Err(ErrorCode::DeadlineNotReached.into());
+ }
+ if ctx.accounts.fund.to_account_info().lamports() < ctx.accounts.fund.goal {
+ return Err(ErrorCode::GoalNotReached.into());
+ }
**ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.fund.to_account_info().lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
**ctx.accounts.creator.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.creator.to_account_info().lamports()
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}