RustFund

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

Missing Fund Balance Update in refund Function Leads to Stuck Funds

Summary

The refund function fails to update the amount_raised variable in the fund account. Due to this the funds will be locked permanently.

Vulnerability Details

When a contributor requests a refund, the refund fundtion transfers the SOL from the fund account back to the contributor and resets the contributor's contribution.amount to 0. However, the function fails to decrease the fund.amount_raised value.

pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
let amount = ctx.accounts.contribution.amount;
// Transfer SOL from fund to contributor
**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)?;
// Reset contribution amount after refund
ctx.accounts.contribution.amount = 0;
// @audit --> Missing: fund.amount_raised -= amount;
Ok(())
}

The direct impact is that the fund.amount_raised variable becomes inconsistent with the actual SOL balance of the fund account. This discrepancy causes issues in the withdraw function.

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
**ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.fund.to_account_info().lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
}

Attack Scenario

  1. A fund is created with a goal of 100 SOL.

  2. Contributors A,B and C each contribute 40 SOL, bringing the total to 120 SOL.
    2.1 fund.amount_raised is now 120 SOL.

  3. Contributor C requests a refund of their 40 SOL.
    3.1 The refund succeeds, transferring 40 SOL back to the Contributor C
    3.2 The fund account now actually contains 80 SOL
    3.3 However, fund.amount_raised incorrectly remains at 120 SOL.

  4. The fund creator attempts to withdraw funds using the withdraw function.
    4.1 The Function tries to withdraw 120 SOL (the value of fund.amount_raised)
    4.2 This fails with ProgramError::InsufficientFunds because the account only has 80 SOL

  5. The remaining 80 SOL becomes loced in the fund account.

Impact

High

Tools Used

Manual Review

Recommendations

pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
let amount = ctx.accounts.contribution.amount;
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)?;
// Reset contribution amount after refund
ctx.accounts.contribution.amount = 0;
fund.amount_raised -= amount
Ok(())
}
Updates

Appeal created

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

`amount_raised` not updated in `refund` function

Support

FAQs

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