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