RustFund

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

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

Summary

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)

// 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)
*/

Tools Used

  • Anchor Framework (JavaScript tests)

  • Manual Code Review

Recommendations

Add conditional logic to the withdraw function to ensure the campaign has reached its fundraising goal before allowing withdrawals:

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:

#[error_code]
pub enum ErrorCode {
// existing errors...
+ #[msg("Campaign goal not met")]
+ GoalNotMet,
}
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.