RustFund

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

Unutilized Fundraising Goal in rustfund Contract

Summary

The rustfund smart contract includes a goal field in the Fund account intended to represent the target fundraising amount. However, this field is never used in the program’s logic, rendering it ineffective. This vulnerability allows the contract to operate without enforcing critical crowdfunding constraints, such as stopping contributions once the goal is reached or restricting withdrawals unless the goal is met. This oversight undermines the contract’s intended functionality and exposes users to potential misuse by the creator.

Vulnerability Details

In the fund_create function, the goal parameter (type u64) is assigned to the fund.goal field of the Fund account

Despite being stored fund.goal is not referenced anywhere else in the program, including key functions like contribute withdraw refund, or set_deadline. This lack of enforcement means:

  • Contributions can exceed the goal without restriction.

  • The creator can withdraw all funds (amount_raised) at any time, even if the goal isn’t met.

  • Refunds are triggered solely by the deadline, ignoring whether the goal was achieved.

This contradicts the typical expectations of a crowdfunding mechanism, where the goal should influence operational logic (e.g., success/failure conditions). The absence of such checks suggests an incomplete implementation or a critical design flaw.

Affected Lines of Code:

pub fn fund_create(ctx: Context<FundCreate>, name: String, description: String, goal: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
fund.name = name;
fund.description = description;
fund.goal = goal; // =============== not referenced anywhere in the code
fund.deadline = 0;
fund.creator = ctx.accounts.creator.key();
fund.amount_raised = 0;
fund.dealine_set = false;
Ok(())
}

Proof of Concept

Consider the following scenario:

  1. A creator initializes a fund with:

    • name = "Project X"

    • description = "Raise funds for X"

    • goal = 100 SOL

    • deadline = 0 (unset initially) Using: fund_create(ctx, "Project X", "Raise funds for X", 100).

  2. Contributors invoke contribute multiple times, raising fund.amount_raised to 150 SOL (exceeding the goal).

    • No check in contribute stops this:

    • fund.amount_raised += amount; executes regardless of fund.goal.

  3. The creator calls withdraw before setting a deadline:

    • withdraw transfers all 150 SOL to the creator’s account (ctx.accounts.creator), despite the goal being only 100 SOL, because no condition like if fund.amount_raised < fund.goal exists.

  4. This demonstrates that fund.goal has no bearing on the contract’s behavior, allowing overfunding and premature withdrawals.

Impact

The unutilized fund.goal has significant consequences:

  • User Trust: Contributors expect a crowdfunding campaign to respect its stated goal. Without enforcement, they may overfund a project unnecessarily or lose funds to a creator who withdraws early, eroding trust in the platform.

  • Financial Risk: Contributors have no guarantee that the project adheres to its fundraising target. A malicious creator could set a low goal (e.g., 1 SOL), collect far more, and withdraw everything without justification.

  • Contract Integrity: The lack of goal enforcement undermines the contract’s purpose as a crowdfunding tool, potentially leading to legal or reputational issues for the deploying entity.

Tools Used

manual Review

Recommendations

To mitigate this vulnerability, integrate fund.goal into the contract’s logic as follows:

  1. Cap Contributions: Modify contribute to prevent overfunding:

    pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
    let fund = &mut ctx.accounts.fund;
    if fund.amount_raised + amount > fund.goal {
    return Err(ErrorCode::GoalExceeded.into());
    }
    // Existing transfer and update logic
    fund.amount_raised += amount;
    Ok(())
    }

Add to ErrorCode enum: GoalExceeded

  • Restrict Withdrawals: Ensure the creator can only withdraw if the goal is met

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let fund = &mut ctx.accounts.fund;
if fund.amount_raised < fund.goal {
return Err(ErrorCode::GoalNotReached.into());
}
let amount = fund.amount_raised;
// Existing transfer logic
fund.amount_raised = 0; // Reset to prevent re-withdrawal
Ok(())
}

Add to ErrorCode enum: GoalNotReached,.

  • Enhance Refunding Logic: Allow refunds if the deadline passes and the goal isn’t met

pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
let fund = &mut ctx.accounts.fund;
if fund.deadline != 0 && fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineNotReached.into());
}
if fund.amount_raised >= fund.goal {
return Err(ErrorCode::GoalReached.into());
}
// Existing refund logic
Ok(())
}

Add to ErrorCode enum: GoalReached,

Updates

Appeal created

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

There is no check for goal achievement in `refund` function

No goal achievement check in `withdraw` function

[Invalid] The contributions are allowed even after the campaign's goal is reached

Typically the crowdfunding campaigns allow contribution after the goal is achieved. This is normal, because the goal is the campaign to raise as much as possible funds. Therefore, this is a design choice.

Support

FAQs

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