RustFund

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

Funds Locked After Goal Reached Due to Insufficient Contribution Check

01. Relevant GitHub Links

02. Summary

The contribute function allows users to contribute to a fund as long as the deadline has not passed. However, it lacks a check to prevent contributions after the fund’s goal is reached. This leads to funds being locked, as the creator can only withdraw the amount_raised at the time of withdrawal, leaving any subsequent contributions inaccessible.

03. Vulnerability Details

Users can contribute to a fund using the contribute function, which only checks if the fund.deadline has not passed. There is no validation to stop contributions once the fund’s goal is met. After the goal is reached, the fund creator can withdraw the total amount_raised at that point.

However, if additional contributions are made afterward, the amount_raised increases, but the creator cannot withdraw these new funds because the withdrawal logic only transfers the fixed amount_raised value from the initial withdrawal. This results in the excess funds being permanently locked in the contract.

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

Additionally, if a large amount of assets is contributed just before the goal is reached, the total funds raised may exceed the goal. However, since this value is correctly added to ctx.accounts.fund.amount_raised and the fund creator can withdraw it without any issues, this does not pose a problem.

04. Impact

If users contribute to a fund after its goal is reached, those additional funds become locked in the contract. Neither the creator nor the contributors can access them, leading to a permanent loss of assets. This could result in significant financial loss, especially in high-value campaigns, and erode trust in the system.

05. Tools Used

Manual Code Review and Foundry

06. Recommended Mitigation

Add a check in the contribute function to prevent contributions once the goal is reached. This can be done by comparing amount_raised against goal and rejecting the transaction if the goal is met or exceeded.

pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
let contribution = &mut ctx.accounts.contribution;
+ if ctx.accounts.fund.goal <= ctx.accounts.fund.amount_raised {
+ return Err(ErrorCode::GoalReached.into());
+ }
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(())
}
#[error_code]
pub enum ErrorCode {
#[msg("Deadline already set")]
DeadlineAlreadySet,
#[msg("Deadline reached")]
DeadlineReached,
#[msg("Deadline not reached")]
DeadlineNotReached,
#[msg("Unauthorized access")]
UnauthorizedAccess, // @question : 사용 안되는데?
#[msg("Calculation overflow occurred")]
CalculationOverflow,
+ #[msg("Goal Reached")]
+ GoalReached,
}

Alternatively, if the fund owner wants to continue receiving contributions even after reaching the goal, the contribute function can check whether the owner has already withdrawn funds. If the withdrawal has not yet occurred, contributions can still be accepted.

Updates

Appeal created

bube Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

[Invalid] The contributions are allowed even after the campaign's goal is reached

Typically the crowdfunding campaigns allow contribution after the goal is achieved. This is normal, because the goal is the campaign to raise as much as possible funds. Therefore, this is a design choice.

Support

FAQs

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