Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

RustFund Security Audit

Executive Summary

Audit of RustFund anchor program revealing 3 high severity vulnerabilities and 1 medium severity vulnerability.

Scope

  • File: src/lib.rs

Risk Rating

High Risk

High Severity Issues

H-1: Unrestricted Funds Withdrawal (Rug Pull)

Description

  • The withdraw function lacks any validation checks regarding the campaign's success.

  • The creator can withdraw funds immediately after they are contributed, regardless of whether the funding goal was met or the deadline has passed.

  • This completely undermines the crowdfunding trust model.

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
// ... transfers funds ...
// No checks for goal completion or deadline
}

Risk

Likelihood: High. A malicious creator can easily drain all funds.
Impact: Contributor funds can be stolen immediately.

Proof of Concept

  1. Creator starts a fund with a goal of 100 SOL.

  2. User contributes 10 SOL.

  3. Creator immediately calls withdraw and receives 10 SOL.

  4. User cannot refund.

Recommended Mitigation

Add checks to ensure the goal has been met and the deadline has passed (optional, depending on design) before allowing withdrawal.

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
+ let fund = &ctx.accounts.fund;
+ require!(fund.amount_raised >= fund.goal, ErrorCode::GoalNotMet);
+
let amount = ctx.accounts.fund.amount_raised;
// ...
}

H-2: Contribution Amount Not Recorded

Description

  • In the contribute function, the contribution.amount state is never updated with the new contribution amount.

  • It is initialized to 0 and stays 0.

  • Only fund.amount_raised is updated.

// Initialize or update contribution record
if contribution.contributor == Pubkey::default() {
// ...
@> contribution.amount = 0;
}
// ... transfer logic ...
@> fund.amount_raised += amount;
// MISSING: contribution.amount += amount;

Risk

Likelihood: Certain (100%). Every contribution fails to record.
Impact: Users can never receive a refund because the refund function calculates the refund amount from contribution.amount (which is 0). Users' funds are permanently locked in the contract if the goal is not met.

Proof of Concept

  1. User contributes 5 SOL.

  2. fund.amount_raised increases by 5.

  3. contribution.amount remains 0.

  4. Deadline passes, goal not met.

  5. User calls refund.

  6. Code executes let amount = ctx.accounts.contribution.amount; (which is 0).

  7. Transfers 0 SOL.

Recommended Mitigation

Update the individual contribution record.

system_program::transfer(cpi_context, amount)?;
fund.amount_raised += amount;
+ contribution.amount += amount;
Ok(())

H-3: Deadline Setter Logic Broken

Description

  • The set_deadline function checks the dealine_set flag to prevent multiple deadline updates, but it never sets this flag to true.

  • Consequently, the creator can call set_deadline indefinitely.

pub fn set_deadline(ctx: Context<FundSetDeadline>, deadline: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
if fund.dealine_set {
return Err(ErrorCode::DeadlineAlreadySet.into());
}
fund.deadline = deadline;
// MISSING: fund.dealine_set = true;
Ok(())
}

Risk

Likelihood: High.
Impact: Creator can malicious extend the deadline indefinitely to hold funds hostage or shorten it instantly to prevent refunds (if combined with other logic).

Proof of Concept

  1. Creator sets a deadline for tomorrow.

  2. Tomorrow comes. Users try to refund.

  3. Creator calls set_deadline again to next year.

  4. Users cannot refund.

Recommended Mitigation

Update the boolean flag after setting the deadline.

fund.deadline = deadline;
+ fund.dealine_set = true;
Ok(())

Medium Severity Issue

M-1: Violation of Checks-Effects-Interactions (CEI) in refund

Description

  • The refund function performs the fund transfer (Interaction) before updating the state (Effect).

  • While less critical in Solana than EVM due to the account model, it is a bad practice and can lead to reentrancy vulnerabilities if complex CPIs are involved.

@> **ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? = ... // Transfer
@> **ctx.accounts.contributor.to_account_info().try_borrow_mut_lamports()? = ...
// Reset contribution amount after refund
@> ctx.accounts.contribution.amount = 0; // State Update

Risk

Likelihood: Low (in this specific simple program).
Impact: Potential for reentrancy or state desynchronization.

Recommended Mitigation

Update the state before performing the transfer.

- **ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? = ...
+ ctx.accounts.contribution.amount = 0;
+
+ **ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? = ...
// ... perform transfer ...
- ctx.accounts.contribution.amount = 0;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 9 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!