RustFund

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

Incorrect Tracking of contribution.amount in contribute Leads to Denial of Refund in refund

Summary

This vulnerability in lib.rs prevents contributors from receiving refunds due to an incorrect state in the contribution account, effectively locking funds in the program or denying legitimate refund requests. It could also damage trust and usability of the system.

The contribute function initializes contribution.amount to 0 and does not update it with the actual contributed amount, while the refund function relies on contribution.amount to determine the refund amount.

This mismatch results in:

  • Contributors receiving a refund of 0 lamports, even after contributing a non-zero amount.

  • A severe logical flaw that undermines the refund mechanism.

  • Potential financial loss to users and legal risks.

Vulnerability Details

In the contribute function, the contribution.amount field is set to 0 during initialization and never updated to reflect the contributed amount, despite the successful transfer of SOL to the fund account and the increment of fund.amount_raised. The refund function, however, uses contribution.amount as the basis for calculating and transferring the refund amount back to the contributor. Since contribution.amount remains 0, the refund process transfers 0 lamports, effectively denying contributors their funds.

Affected Code in contribute

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());
}
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
contribution.amount = 0; // Vulnerability: Set to 0 and never updated
}
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(())
}

Affected Code in refund

pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
let amount = ctx.accounts.contribution.amount; // Uses contribution.amount, which is 0
if ctx.accounts.fund.deadline != 0 && ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineNotReached.into());
}
**ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.fund.to_account_info().lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
**ctx.accounts.contributor.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.contributor.to_account_info().lamports()
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
ctx.accounts.contribution.amount = 0; // Resets to 0 after refund
Ok(())
}

Root Cause

  • In contribute, contribution.amount is initialized to 0 and never incremented.

  • In refund, the refund amount is taken from contribution.amount (always 0).

  • This disconnect breaks the refund mechanism.

Impact

  • Financial Loss: Contributors cannot reclaim funds via refund.

  • Program Integrity: Refund mechanism is non-functional.

  • Reputation Damage: Loss of user trust, potential legal disputes.

  • Denial-of-Service: Legitimate users are locked out of refunds.

Tools Used

Manual review

Recommendations

Update contribute to track individual contributions:

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());
}
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
contribution.amount = 0; // Initialize to 0 for new contributor
}
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;
contribution.amount += amount; // Fix: Track individual contribution
Ok(())
}
Updates

Appeal created

bube Lead Judge 2 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.