Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Severity: high
Valid

Withdraw Function Lacks Goal Validation

Root + Impact

Description

  • Describe the normal behavior in one or more sentences
    The `withdraw` function allows the creator to withdraw funds without checking if the funding goal was met. According to the project requirements, creators should only be able to withdraw funds after their campaign succeeds (goal is met). Currently, creators can withdraw funds even if the goal was never reached.

  • Explain the specific issue or problem in one or more sentences
    The normal behavior should only allow withdrawals when `amount_raised >= goal`, ensuring that creators can only access funds from successful campaigns. However, the current implementation has no validation, allowing withdrawals regardless of campaign success.

```rust:90-105:programs/rustfund/src/lib.rs
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(())
}
```
The function immediately withdraws all raised funds without checking if `fund.amount_raised >= fund.goal`.

Risk

Likelihood:

  • * This occurs every time `withdraw` is called - no validation is performed

    * Creators can withdraw funds immediately after creating a campaign, even with 0 contributions

Impact:

* Creators can withdraw funds from failed campaigns, breaking the refund mechanism

  • * Contributors lose their ability to get refunds if creator withdraws before deadline

    * Financial loss for contributors who expected refunds for failed campaigns

    * Breaks the core trust model of the crowdfunding platform

Proof of Concept

1. Creator creates campaign with goal: 10 SOL
2. Contributors contribute 2 SOL total (goal not met)
3. Creator immediately calls `withdraw()` - no goal check
4. Creator receives 2 SOL
5. Deadline passes, contributors try to refund
6. Fund account is empty, refunds fail
7. Contributors lose their 2 SOL

Recommended Mitigation

```diff
pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
+ // Check if goal was met
+ if ctx.accounts.fund.amount_raised < ctx.accounts.fund.goal {
+ return Err(ErrorCode::GoalNotMet.into());
+ }
+
+ // Check if deadline has passed (optional, depending on requirements)
+ // If withdrawals should only happen after deadline:
+ // if ctx.accounts.fund.deadline != 0 && ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
+ // return Err(ErrorCode::DeadlineNotReached.into());
+ // }
+
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)?;
+ // Reset amount_raised to prevent double withdrawal
+ ctx.accounts.fund.amount_raised = 0;
Ok(())
}
```
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 16 days ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-02] H-01. Creators Can Withdraw Funds Without Meeting Campaign Goals

# H-01. Creators Can Withdraw Funds Without Meeting Campaign Goals **Severity:** High\ **Category:** Fund Management / Economic Security Violation ## Description The `withdraw` function in the RustFund contract allows creators to prematurely withdraw funds without verifying if the campaign goal was successfully met. ## Vulnerability Details In the current RustFund implementation (`lib.rs`), the `withdraw` instruction lacks logic to verify that the campaign's `amount_raised` is equal to or greater than the `goal`. Consequently, creators can freely withdraw user-contributed funds even when fundraising objectives haven't been met, undermining the core economic guarantees of the platform. **Vulnerable Component:** - File: `lib.rs` - Function: `withdraw` - Struct: `Fund` ## Impact - Creators can prematurely drain user-contributed funds. - Contributors permanently lose the ability to receive refunds if the creator withdraws early. - Severely damages user trust and undermines the economic integrity of the RustFund platform. ## Proof of Concept (PoC) ```js // Create fund with 5 SOL goal await program.methods .fundCreate(FUND_NAME, "Test fund", new anchor.BN(5 * LAMPORTS_PER_SOL)) .accounts({ fund, creator: creator.publicKey, systemProgram: SystemProgram.programId, }) .signers([creator]) .rpc(); // Contribute only 2 SOL (below goal) await program.methods .contribute(new anchor.BN(2 * LAMPORTS_PER_SOL)) .accounts({ fund, contributor: contributor.publicKey, contribution, systemProgram: SystemProgram.programId, }) .signers([contributor]) .rpc(); // Set deadline to past await program.methods .setDeadline(new anchor.BN(Math.floor(Date.now() / 1000) - 86400)) .accounts({ fund, creator: creator.publicKey }) .signers([creator]) .rpc(); // Attempt withdrawal (should fail but succeeds) await program.methods .withdraw() .accounts({ fund, creator: creator.publicKey, systemProgram: SystemProgram.programId, }) .signers([creator]) .rpc(); /* OUTPUT: Fund goal: 5 SOL Contributed amount: 2 SOL Withdrawal succeeded despite not meeting goal Fund balance after withdrawal: 0.00089088 SOL (rent only) */ ``` ## Recommendations Add conditional logic to the `withdraw` function to ensure the campaign has reached its fundraising goal before allowing withdrawals: ```diff pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> { let fund = &mut ctx.accounts.fund; + require!(fund.amount_raised >= fund.goal, ErrorCode::GoalNotMet); let amount = 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(()) } ``` Also define the new error clearly: ```diff #[error_code] pub enum ErrorCode { // existing errors... + #[msg("Campaign goal not met")] + GoalNotMet, } ```

Support

FAQs

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

Give us feedback!