Rust Fund

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

Missing input validations for zero goal, zero contribution amount, and unsafe timestamp casting

Description

The contract omits several critical boundary input validations:

  1. Zero Goal in fund_create: goal can be initialized to 0. A campaign with goal == 0 is considered funded immediately upon creation.

  2. Zero Amount in contribute: contribute does not check require!(amount > 0). Any user can spam zero-value contributions, allocating unneeded Contribution PDA accounts and cluttering state.

  3. Double .unwrap() on Timestamp: Clock::get().unwrap().unix_timestamp.try_into().unwrap() invokes .unwrap() twice. In Solana programs, if clock sysvar retrieval or type casting fails, it triggers a raw panic instead of a gracefully handled Anchor error.

pub fn fund_create(ctx: Context<FundCreate>, name: String, description: String, goal: u64) -> Result<()> {
@> // @> Missing: require!(goal > 0, ErrorCode::InvalidGoal);
let fund = &mut ctx.accounts.fund;
fund.name = name;
fund.description = description;
fund.goal = goal;
...
}
​
pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
@> // @> Missing: require!(amount > 0, ErrorCode::ZeroAmount);
let fund = &mut ctx.accounts.fund;
let contribution = &mut ctx.accounts.contribution;
@> if fund.deadline != 0 && fund.deadline < Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineReached.into());
}
...
}

Risk

Likelihood: Medium (Any user or creator can pass zero values)

Impact: Low (Does not cause direct fund loss, but enables state spam and ungraceful panics)

Proof of Concept

  1. A creator calls fund_create(ctx, "Test", "Test", 0). The campaign succeeds immediately with 0 SOL raised.

  2. An attacker repeatedly calls contribute(ctx, 0) with newly generated keypairs, causing the network to allocate empty Contribution PDAs for 0 lamports transferred.

Recommended Mitigation

Add explicit parameter validations and replace raw .unwrap() with Anchor error propagation:

pub fn fund_create(ctx: Context<FundCreate>, name: String, description: String, goal: u64) -> Result<()> {
+ require!(goal > 0, ErrorCode::InvalidGoal);
let fund = &mut ctx.accounts.fund;
...
}
​
pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
+ require!(amount > 0, ErrorCode::ZeroAmount);
let fund = &mut ctx.accounts.fund;
...
- if fund.deadline != 0 && fund.deadline < Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
+ let current_time: u64 = Clock::get()?.unix_timestamp.try_into()
+ .map_err(|_| ErrorCode::CalculationOverflow)?;
+ if fund.deadline != 0 && fund.deadline < current_time {
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour 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!