RustFund

First Flight #36
Beginner FriendlyRust
100 EXP
View results
Submission Details
Severity: high
Valid

Business Logic Error – Withdrawals Allowed Without Meeting Goal

Summary

The withdraw function in the rustfund smart contract does not verify whether the fundraising goal has been met before allowing withdrawal. This creates a business logic flaw where fund creators can withdraw funds prematurely, even if the campaign has only raised a small fraction of the goal.

This violates the expected behavior of a crowdfunding platform, where funds should only be withdrawn if the campaign is fully funded. If exploited, donors could be misled, leading to fund mismanagement, loss of credibility, and financial fraud.

Vulnerability Details

Vulnerable Code:

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(())
}

Issue:

  • There is no validation to check whether the fundraising goal has been reached.

  • A malicious campaign creator could withdraw funds even if only 1 SOL was raised instead of the required amount.

  • Donors expect that funds will only be withdrawn if the goal is met, but this assumption is broken.

Impact

Loss of donor trust – Contributors assume their money will only be used if the campaign succeeds.

  • Fraudulent fundraising – Scammers can withdraw funds early without delivering on their promises.

  • Misallocation of funds – A campaign raising only 10% of its goal can still withdraw, potentially leaving projects underfunded.

Tools Used

Manually

Recommendations

Fixed Code:

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
require!(ctx.accounts.fund.amount_raised >= ctx.accounts.fund.goal, ErrorCode::GoalNotReached);
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(())
}

}

Updates

Appeal created

bube Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

No goal achievement check in `withdraw` function

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.