Rust Fund

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

Contributors Lose All Funds Due to `contribution.amount` Never Being Updated

Severity Impact Likelihood
CRITICAL High High

Scope: programs/rustfund/src/lib.rs:25-52

Description

In a crowdfunding platform, contributors should be able to get refunds if the campaign fails to meet its goal. The Contribution account stores the amount each user contributed via contribution.amount, which is then used in the refund() function to return funds.

The contribute() function transfers SOL from the contributor to the fund and increments fund.amount_raised, but never updates contribution.amount. This means the contribution tracking is broken - all contributions are recorded as 0, making refunds worthless.

pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
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());
}
// Initialize or update contribution record
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
contribution.amount = 0; // Initialized to 0
}
// Transfer SOL from contributor to fund account
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.contributor.to_account_info(),
to: fund.to_account_info(),
},
);
system_program::transfer(cpi_context, amount)?;
fund.amount_raised += amount;
// @> BUG: Missing contribution.amount += amount;
Ok(())
}

Risk

Likelihood: High

  • Every single contribution to any campaign triggers this bug

  • The missing line of code means 100% of contributions are affected

Impact: High

  • Contributors permanently lose all contributed SOL with no way to recover

  • Refunds return 0 lamports regardless of actual contribution amount

  • Complete loss of user funds - the most severe impact category

Proof of Concept (PoC)

it("Victim contributes 1 SOL but contribution.amount stays 0", async () => {
// Victim contributes 1 SOL
await program.methods
.contribute(new anchor.BN(1 * LAMPORTS_PER_SOL))
.accounts({
fund: fundPDA,
contributor: victim.publicKey,
contribution: contributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([victim])
.rpc();
// Check contribution account
const contribution = await program.account.contribution.fetch(contributionPDA);
// BUG: contribution.amount is 0 despite sending 1 SOL
expect(contribution.amount.toNumber()).to.equal(0);
// Later, refund() will return 0 SOL to the victim
});

Mitigation

pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
// ... transfer logic ...
fund.amount_raised += amount;
+ ctx.accounts.contribution.amount += amount;
Ok(())
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 5 days 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!