RustFund

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

Fund Creator Can't Withdraw If Someone Has Refunded Their Contribution

[H-02] Fund Creator Can't Withdraw If Someone Has Refunded Their Contribution

Summary

The refund function does not update fund.amount_raised, causing an inconsistency between the fund's actual balance and the recorded raised amount. As a result, when the fund creator tries to withdraw funds, the transaction may fail due to insufficient balance, effectively locking funds in the contract.

Vulnerability Details

The issue arises in the refund function, which transfers funds back to the contributor but does not update the amount_raised field:

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

The issue becomes evident when the fund creator attempts to withdraw using the following 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)?;
ctx.accounts.creator.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.creator.to_account_info().lamports()
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}

Since amount_raised is never updated when a refund occurs, the creator will attempt to withdraw more than what actually exists in the fund, causing an insufficient funds error and failing the transaction.

Impact

  • If any contributor requests a refund, the total balance in the fund decreases. However, fund.amount_raised remains unchanged, leading to an overestimated available balance.

  • When the fund creator calls withdraw, they attempt to transfer fund.amount_raised, which no longer matches the actual available balance.

  • This results in a failed transaction, effectively locking funds in the contract since the withdraw function will always fail if refunds have been processed.

Proof of Concept

This issue is not currently caught by tests because the contribute function itself has a bug (not updating contribution.amount), preventing the refund function from executing properly. Once the contribute function is fixed, the issue will be clearly visible in test cases.

Tools Used

  • Manual Review

Recommendations

The refund function must update fund.amount_raised to ensure the contract state reflects the actual balance after refunds.

Fixed Code:

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;
+ // Fix: Decrease the fund's recorded amount_raised
+ let fund = &mut ctx.accounts.fund;
+ fund.amount_raised = fund.amount_raised.checked_sub(amount).ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}
Updates

Appeal created

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