Rust Fund

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

`fund_create()` accepts `goal = 0` making the fund immediately withdrawable without any contributions

Root + Impact

Description

  • fund_create initializes a campaign with a fundraising goal that contributors expect must be reached before the creator can withdraw.

  • The creation instruction accepts goal: u64 with no lower bound check. A goal of zero means fund.amount_raised >= fund.goal evaluates to 0 >= 0 = true from the moment the account is created — before any contributions arrive. Any goal-check withdraw guard added to fix H2 is instantly bypassed, and a creator can present a zero-goal campaign to collect contributions then withdraw immediately while the contract considers the goal met.

  • This is an independent validation gap in fund_create() that exists regardless of whether the withdraw guard from H2 is applied.

pub fn fund_create(
ctx: Context<FundCreate>,
name: String,
description: String,
goal: u64
) -> Result<()> {
fund.goal = goal; // no require!(goal > 0)
// ...
}

Risk

Likelihood:

  • A creator deliberately deploying a zero-goal campaign to immediately pass any goal-check guard is not constrained by any on-chain validation — it requires only the intent to do so.

  • Input validation for the goal field is a standard precondition check that is missing here, making the oversight likely to be hit during normal usage as well.

Impact:

  • The goal-check protection added to fix H2 is rendered entirely ineffective for any campaign where the creator sets goal = 0.

  • Contributors have no protection on zero-goal campaigns; the withdraw invariant is violated from the moment of fund creation.

Proof of Concept

Place this test in tests/ and run anchor test. The test demonstrates that a fund created with goal = 0 immediately satisfies the goal-reached condition without any contributions, bypassing all goal-based withdraw guards.

it("fund with goal=0 satisfies goal check immediately with no contributions", async () => {
// Create fund with goal = 0
await program.methods
.fundCreate("Zero Goal Fund", "desc", new BN(0))
.accounts({ fund, creator: creator.publicKey })
.signers([creator])
.rpc();
const fundAccount = await program.account.fund.fetch(fund);
// 0 >= 0 is true — goal check passes with no contributions
assert.ok(fundAccount.amountRaised.gte(fundAccount.goal));
});

Recommended Mitigation

Add require!(goal > 0, ErrorCode::InvalidGoal) at the top of fund_create() to ensure every campaign has a meaningful fundraising target.

pub fn fund_create(
ctx: Context<FundCreate>,
name: String,
description: String,
goal: u64
) -> Result<()> {
+ require!(goal > 0, ErrorCode::InvalidGoal);
fund.goal = goal;
// ...
}

Add InvalidGoal to the ErrorCode enum:

#[error_code]
pub enum ErrorCode {
#[msg("Deadline already set")]
DeadlineAlreadySet,
#[msg("Deadline reached")]
DeadlineReached,
#[msg("Deadline not reached")]
DeadlineNotReached,
#[msg("Unauthorized access")]
UnauthorizedAccess,
#[msg("Calculation overflow occurred")]
CalculationOverflow,
+ #[msg("Goal must be greater than zero")]
+ InvalidGoal,
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Validated
Assigned finding tags:

[L-02] No Fund Goal Validation

## Description The contract allows fund creation with a goal amount of zero, which could be misleading to contributors. ## Vulnerability Details The fund_create function doesn't validate that the goal amount is reasonable (greater than zero), allowing the creation of funds with meaningless fundraising goals. ```rust 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; // No validation that goal > 0 fund.deadline = 0; fund.creator = ctx.accounts.creator.key(); fund.amount_raised = 0; fund.dealine_set = false; Ok(()) } ``` ## Impact Funds with zero goals could confuse contributors and potentially be used to trick users by making it unclear when the funding target has been reached. ## POC Add to tests/rustfund.ts: ```javascript //audit LOW - No Fund Goal Validation it("Can create a fund with zero goal", async () => { const zeroGoalFundName = "Zero Goal Fund"; const [zeroGoalFundPDA] = await PublicKey.findProgramAddress( [Buffer.from(zeroGoalFundName), creator.publicKey.toBuffer()], program.programId ); await program.methods .fundCreate(zeroGoalFundName, description, new anchor.BN(0)) .accounts({ fund: zeroGoalFundPDA, creator: creator.publicKey, systemProgram: anchor.web3.SystemProgram.programId, }) .rpc(); const fund = await program.account.fund.fetch(zeroGoalFundPDA); console.log(`Created fund with goal amount: ${fund.goal.toString()}`); }); ``` Output: ```javascript ======================================== 🐛 BUG REPORT [LOW]: No Fund Goal Validation ---------------------------------------- Description: The program allows creating funds with zero or invalid goal amounts Evidence: Created fund with goal amount: 0 ======================================== ``` ## Recommendations Add validation to ensure the goal is greater than zero: ```diff pub fn fund_create(ctx: Context<FundCreate>, name: String, description: String, goal: u64) -> Result<()> { // Validate goal is greater than zero + if goal == 0 { + return Err(ErrorCode::InvalidGoalAmount.into()); } let fund = &mut ctx.accounts.fund; fund.name = name; fund.description = description; fund.goal = goal; fund.deadline = 0; fund.creator = ctx.accounts.creator.key(); fund.amount_raised = 0; fund.dealine_set = false; Ok(()) } ```

Support

FAQs

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

Give us feedback!