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