RustFund

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

Silent-Fall in the contribute function: `contribution.amount` field is not updated.

Summary

The contribute function fails to record the contributor’s deposited amount in their Contribution account, causing a silent logic failure. The contribution transaction succeeds (no error), but the Contribution.amount remains zero. This bug effectively bypasses the refund mechanism, since the contract has no record of the contribution amount to refund. Contributors who funded a campaign cannot get their money back via the refund instruction because the contract thinks they contributed 0.

Vulnerability Details

In the contribute instruction handler, the code updates the campaign’s total funds raised (fund.amount_raised) but never updates the contributor’s own contribution amount. For example, after a user contributes 5 SOL, the global Fund state reflects the increase, but the corresponding Contribution account still shows amount = 0. This is a silent failure – the transaction doesn’t revert, so from the user’s perspective the contribution appears to have succeeded, when in reality the contract state for that contributor is incorrect.

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;
}
// 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;
Ok(())
}

It likely remains at the default value 0 because the program did not set it during initialization or subsequent contributions. Consequently, any logic that relies on Contribution.amount will not function as intended. One such logic is the refund process. The refund instruction presumably uses the stored contribution amount to determine how much SOL to return to the contributor. Because of this bug, when a contributor attempts to refund, the program will see their recorded contribution as 0 and thus transfer 0 lamports back. The code does not throw an error; it simply does nothing (transferring zero lamports is effectively a no-op), silently bypassing the intended refund payout. The contributor’s refund attempt will "succeed" from the program’s perspective but result in no funds returned.

Impact Details

This flaw breaks the core promise of the crowdfunding platform: contributors to failed campaigns cannot actually reclaim their funds. In a failure scenario (goal not met by deadline), contributors expect to call refund to get their money back. However, due to the untracked contribution amount, the contract will not return any funds (since it believes the contribution was 0). This means contributors’ funds remain locked in the campaign fund, even after the campaign fails, unless the campaign creator manually intervenes (which is against the protocol’s intent). Furthermore, this silent failure could be exploited in combination with other issues. For instance, a malicious campaign creator could deliberately leave this bug unfixed to deny refunds to contributors and then withdraw those "stuck" funds for themselves. Contributors have no on-chain evidence of their contribution amount, so the contract cannot enforce fair refunds. This issue severely damages user trust and effectively results in loss of funds for honest contributors in the event of a campaign failure.

Tools Used

Manual code analysis (viewing lines with initialization and updating of the Contribution.amount field).

Recommendations

Fix the function so that contribution.amount = amount is set on the first call, and addition is performed on repeated calls (for example, contribution.amount += amount with overflow check).

Updates

Appeal created

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

Contribution amount is not updated

Support

FAQs

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