Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Creator can withdraw funds at any time without reaching funding goal or passing deadline

Root + Impact

Description

The protocol specification dictates that creators should only be allowed to withdraw funds once their fundraising campaign succeeds (goal reached).

However, the withdraw() function contains zero validation checks regarding fund.goal or fund.deadline. A creator can call withdraw() immediately after any contributor deposits SOL, stealing the funds even if the campaign raised far less than the required goal or before the campaign deadline has elapsed.

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

Risk

Likelihood:

High. Any malicious or premature creator can execute withdraw() at any moment during an active campaign.

Impact:

High. Theft of contributor funds on failed or incomplete campaigns. Contributors are deprived of their promised refunds on unsuccessful fundraisers.

Proof of Concept

A campaign is created with a 1,000 SOL goal. A contributor deposits 10 SOL. The creator immediately calls withdraw(), draining the 10 SOL even though the campaign is far from reaching its target.

// 1. Creator creates fund with goal = 1_000 SOL.
// 2. Contributor deposits 10 SOL (amount_raised = 10 SOL).
// 3. Creator immediately invokes withdraw().
// 4. Function transfers all 10 SOL to creator despite amount_raised < goal.

Recommended Mitigation

Ensure withdraw() verifies that the campaign has reached or exceeded its funding goal.

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
+ require!(
+ ctx.accounts.fund.amount_raised >= ctx.accounts.fund.goal,
+ ErrorCode::UnauthorizedAccess
+ );
let amount = ctx.accounts.fund.amount_raised;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!